P1197 [JSOI2008]星球大战 题解

题目:P1197 [JSOI2008]星球大战

并查集 - 图论 - 连通块

题目大意

给出一个 n n n 个点, m m m 条边的无向图,并告诉你 k k k 个攻击目标的编号
敌人依次攻击这 k k k 个点,求对于每次攻击后,图被分成了多少个连通块

首先,因为并查集不资瓷分裂操作,我们要考虑倒着做

先用并查集维护出 k k k 个点都摧毁后,图的连通情况

然后,从最后一个被攻击的点开始,我们实行一个修复操作:把这个刚被修复的点与其他 已经修好(或没被攻击过)的点 连边,并用并查集维护连通情况
在修复完每个被攻击的点后,将连通块个数存起来,最后倒序输出

注意:第一行要输出刚开始图的连通块个数

#include<cstdio>
#include<iostream>
#include<vector>
#include<stack>
using namespace std;
const int Maxn=400000+20,inf=0x3f3f3f3f;
vector <int> e[Maxn];
stack <int> s;
int n,m,k,cur;
int f[Maxn],a[Maxn];
bool vis[Maxn];
inline int read()
{
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0' && ch<='9')s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
	return s*w;
}
int find(int x)
{
	if(f[x]==x)return x;
	return f[x]=find(f[x]);
}
int main()
{
	//freopen("in.txt","r",stdin);
	n=read(),m=read();
	for(int i=1;i<=n;++i)
	f[i]=i;
	for(int i=1;i<=m;++i)
	{
		int x=read(),y=read();
		x++,y++;
		e[x].push_back(y);
		e[y].push_back(x);
	}
	k=read();
	for(int i=1;i<=k;++i)
	{
		a[i]=read();
		a[i]++;
		vis[a[i]]=1; //vis[i] = 1,表示城市 i 当前为稀巴烂(未修复)的状态,反之亦然
	}
	// 维护 k 个点都被摧毁时的状态
	for(int i=1;i<=n;++i)
	{
		int x=i;
		if(vis[x])continue;
		for(int j=0;j<e[i].size();++j)
		{
			int y=e[i][j];
			if(vis[y])continue;
			f[find(x)]=find(y);
		}
	}
	for(int i=1;i<=n;++i)
	if(f[i]==i && !vis[i])++cur;
	
	s.push(cur);
	for(int i=k;i>0;--i)
	{
		// 开始修复
		vis[a[i]]=0;
		int x=a[i];
		cur++;
		for(int j=0;j<e[x].size();++j)
		{
			int y=e[x][j];
			if(vis[x] || vis[y] || find(x)==find(y))continue;
			f[find(x)]=find(y);
			--cur;
		}
		s.push(cur);  //利用栈的先进后出,实现倒序
	}
	while(s.size())
	{
		printf("%d\n",s.top());
		s.pop();
	}
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值