对于这道题目,我们不难想到要用并查集来解决,但由于数据范围较大,而且摧毁星球的过程很难在已经建好的并查集内构建,所以我们不妨逆向思考,将摧毁变为重建星球。
读入数据后,我们采用链式邻接表构建两个点之间的边,并初始化所有即将被摧毁的星球为true。初始化并查集。由于单个点也可以作为连通块,所以我们初始化num为n-k,记录在要摧毁的所有星球都被摧毁后连通块的数量。枚举边,如果边的两个端点都没被摧毁并且两点有不同的祖先,就将他们合并,num--。这样我们就求出了输出的最后一行,也就是所有星球都被摧毁时的情况。然后,我们对k个星球逐一进行恢复,循环从k到1(因为是倒着来嘛,所以恢复也是倒着开始的),将该位置所代表的星球变成false代表可以进入并查集,num++(因为单独一个星球也是连通块)。接着邻接表以该星球为出发点(大大节省时间),遍历与它相连的结点,如果可以加入并查集并且祖先不同,就加进去,num--,别忘了一开始把num记录到ans数组里,最后正着输出就行了。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int maxn=400010;
const int maxm=200010;
int n,m,k,tot,q;
int num;
int head[maxn],nnext[maxm*2],to[maxm*2];
int x[maxm],y[maxm],ans[maxm];
int d[maxn];
bool b[maxn];
int fa[maxn];
int find(int x)
{
if(fa[x]!=x) fa[x]=find(fa[x]);
return fa[x];
}
void add(int x,int y)
{
tot++;
nnext[tot]=head[x];
head[x]=tot;
to[tot]=y;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x[i],&y[i]);
add(x[i],y[i]);
add(y[i],x[i]);
}
scanf("%d",&k);
memset(b,false,sizeof(b));
for(int i=1;i<=k;i++)
{
scanf("%d",&d[i]);
b[d[i]]=true;
}
for(int i=0;i<=n;i++)
{
fa[i]=i;
}
int num=n-k;
for(int i=1;i<=m;i++)
{
if(!b[x[i]]&&!b[y[i]]&&find(x[i])!=find(y[i]))
{
num--;
fa[find(x[i])]=find(y[i]);
}
}
for(int i=k;i>=1;i--)
{
b[d[i]]=false;
ans[i]=num;
num++;
for(int j=head[d[i]];j;j=nnext[j])
{
int now=to[j];
if(!b[now])
{
if(find(now)!=find(d[i]))
{
num--;
fa[find(now)]=find(d[i]);
}
}
}
}
cout<<num<<endl;
for(int i=1;i<=k;i++)
{
cout<<ans[i]<<endl;
}
return 0;
}