并查集 - 图论 - 连通块
题目大意
给出一个
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;
}