洛谷P1197 【JSOI2008】星球大战

题目传送门

这道题大部分的人一开始的思路都是一个一个的删除点再求联通块数,但问题是断开一条边并不意味着会有一个联通块断成两个,所以我们只能遍历整个图,而这个做法的时间复杂度是我们接受不了的。

既然删点行不通,我们不妨逆向思考,从最后的状态开始加点,这样只需要用并查集维护联通,然后每次遍历加入的点的连边就行了。如果新加入的点有将两个原来不相连的联通块连接起来的边,那么联通块的数量就会减少。

上代码

#include<iostream>
#include<cstdio>
using namespace std;
const int N=200010;
int h[N<<1],to[N<<1],pre[N<<1];//邻接表
int n,m,q,top,fa[N<<1],d[N<<1],ans[N<<1];
//fa记录并查集,d按顺序记录被摧毁的星球编号,ans[i]记录摧毁了i个星球后联通块数量 
bool vis[N<<1];//记录当前时刻每个星球是否被摧毁,为true则为被摧毁 
int read()//快读 
{
	int sum=0;char ch=getchar();
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9')sum=(sum<<3)+(sum<<1)+ch-'0',ch=getchar();
	return sum;
}
inline void ins(int u,int v)//插入边 
{
	pre[++top]=h[u];h[u]=top;to[top]=v;
}
int sfind(int x)//询问并查集 
{
	if(x!=fa[x])fa[x]=sfind(fa[x]);
	return fa[x];
}
int main()
{
	n=read();m=read();
	for(int i=0;i<n;i++)fa[i]=i;//初始化并查集 
	for(int i=1;i<=m;i++)
	{
		int x=read(),y=read();ins(x,y);ins(y,x);
	}
	q=read();ans[q]=n-q;//在摧毁q颗星球后最多会有(n-q)个联通块 
	for(int i=1;i<=q;i++)
	{
		int x=read();vis[x]=true;d[i]=x;
	}//记录被摧毁的星球
	for(int i=0;i<n;i++)//找到最后时刻的联通块数量 
	{
		if(vis[i])continue;//如果当前点已经被摧毁,那么直接跳过 
		int fx=sfind(i);
		for(int j=h[i];j;j=pre[j])
		{
			if(vis[to[j]])continue;//如果当前边连的点被摧毁就跳过 
			int fy=sfind(to[j]);
			if(fx!=fy)fa[fy]=fx,ans[q]--;//这条边沟通了两个联通块,联通块数量减一 
		}
	}
	for(int i=q;i>0;i--)//倒序计算每个星球被摧毁后的联通块数量 
	{
		vis[d[i]]=false;int fx=sfind(d[i]);//先恢复当前点 
		ans[i-1]=ans[i]+1;//图里加入了一个单独的点,联通块数量加一 
		for(int j=h[d[i]];j;j=pre[j])
		{
			if(vis[to[j]])continue;
			int fy=sfind(to[j]);
			if(fx!=fy)fa[fy]=fx,ans[i-1]--;//连接了两个联通块,答案减一 
		}
	}
	for(int i=0;i<=q;i++)printf("%d\n",ans[i]);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/kdlkswb/article/details/82858123