题目:https://www.luogu.org/problemnew/show/P1197
思路:逆向思维——并查集删边特难,所以采用逆向操作。
开始,n个点看作n个孤立点,记ans[k]=n。然后每增加一个摧毁点,令ans[k]--。
【重要】逆向增加摧毁点,该点记为now,此时ans[i]++,因为now是新增的孤立点。遍历now的相邻点,如果now与它的相邻点祖先不一样,则合并,同时ans[i]--。
采用前向星存图。
采用并查集。
AC代码:
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int MaxM=200005;
int n,m,k,x,y,cui[2*MaxM],ans[2*MaxM],far[2*MaxM];
struct Edge{
int x,y,next;
}edge[2*MaxM];
bool flg[2*MaxM],vis[2*MaxM];
int num,head[2*MaxM],hd,tl,que[2*MaxM];
void joinn(int x,int y){
edge[++num].next=head[x];
edge[num].x=x;
edge[num].y=y;
head[x]=num;
}
int father(int x){
if(far[x]!=x) far[x]=father( far[x] );
return far[x];
}
void unionn(int x,int y){
int xx=father(x),yy=father(y);
if(xx!=yy)far[xx]=yy;
}
void bfs(int now){
hd=0;tl=1;
que[1]=now;
int to;
while(hd<tl){
hd++;
now=que[hd];
for(int i=head[now];i!=-1;i=edge[i].next){
to=edge[i].y;
if(flg[to]==0 && vis[to]==0 ){
flg[to]=1;
unionn(now,to);
ans[k]--;
que[++tl]=to;
}
}
}
}
int main(){
memset(head,-1,sizeof(head));
cin>>n>>m;
for(int i=0;i<n;i++)far[i]=i;
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
joinn(x,y);
joinn(y,x);
}
cin>>k;
ans[k]=n;//初始认为有n个相互孤立的联通块
for(int i=1;i<=k;i++){
scanf("%d",&cui[i]);
vis[ cui[i] ]=true;
ans[k]--;
}
for(int i=0;i<n;i++){//计算连通块
if( flg[i]==0 && vis[i]==0 ){
flg[i]=1;
bfs(i);
}
}
for(int i=k;i>=1;i--){//逆向思维,不断加点计算连通块
ans[i-1]=ans[i];
int now=cui[i];
ans[i-1]++;//点now作为孤立点,故要增加1
vis[now]=0;
for(int j=head[now];j!=-1;j=edge[j].next){
int to=edge[j].y;
if(vis[to]==0 && father(now)!=father(to) ){
ans[i-1]--;
unionn(now,to);
}
}
}
for(int i=0;i<=k;i++)printf("%d\n",ans[i]);
return 0;
}