[JSOI2008]星球大战

题目:BZOJ1015、洛谷P1197。

题目大意:有n个星球,星球之间有隧道连接。

现在依次有一些星球被摧毁,摧毁后就算不存在了,与它连接的隧道也没了。

如果某些星球互相可以到达,我们就说它们在一个连通块内。现在首先输出原来的连通块个数,然后依次输出每次摧毁后这些星球形成的连通块个数。

如果只有一个点,也算一个连通块。

解题思路:如果每次是连接一些星球的话,那我们可以用并查集。但此题是摧毁星球,也就是删除边,怎么办?

容易想到,离线处理询问,先算出最终的状态,然后往上倒着推回来,就是加边的操作了,就可使用并查集。

其他没什么技巧,时间复杂度$O(n+m+k)$。

C++ Code:

#include<cstdio>
#include<cstring>
int n,m,k,bom[400005],head[400005],fa[400005],all[400005];
bool vis[400005];
struct edge{
	int to,nxt;
}e[400005];
int dad(int x){return x==fa[x]?x:(fa[x]=dad(fa[x]));}
int main(){
	//freopen("input","r",stdin);
	int cnt=0;
	memset(head,0,sizeof head);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;++i){
		int u,v;
		scanf("%d%d",&u,&v);
		e[++cnt]=(edge){v,head[u]};
		head[u]=cnt;
		e[++cnt]=(edge){u,head[v]};
		head[v]=cnt;
	}
	int ans=n;
	memset(vis,1,sizeof vis);
	scanf("%d",&k);
	for(int i=1;i<=k;++i)scanf("%d",&bom[i]),vis[bom[i]]=false,--ans;
	for(int i=1;i<=n;++i)fa[i]=i;
	for(int i=1;i<=n;++i)
	if(vis[i]){
		int a=dad(i);
		for(int j=head[i];j;j=e[j].nxt)
		if(vis[e[j].to]){
			int b=dad(e[j].to);
			if(a!=b){
				fa[b]=a;
				--ans;
			}
		}
	}
	all[k]=ans;
	for(int i=k;i;--i){
		vis[bom[i]]=true;
		++ans;
		int a=dad(bom[i]);
		for(int j=head[bom[i]];j;j=e[j].nxt)
		if(vis[e[j].to]){
			int b=dad(e[j].to);
			if(a!=b){
				fa[b]=a;
				--ans;
			}
		}
		all[i-1]=ans;
	}
	for(int i=0;i<=k;++i)printf("%d
",all[i]);
	return 0;
}
原文地址:https://www.cnblogs.com/Mrsrz/p/7813235.html