题目:
https://www.luogu.org/problem/show?pid=1197
明确:无向图求解联通块问题常常用并查集;
所以我们第一反应是并查集
但并查集只能“添加”,不能删除;
注意到原题有:帝国总是按输入的顺序依次摧毁星球的;
所以我们可以考虑反着用并查集,这样就可以只”添加”,而不用删除,在并查集的适用范围中;
思路:
将摧毁顺序反过来;
然后不断向集合里加点
每有一个摧毁的点,联通块个数加1;
如果它可以与某一集合合并,那么联通块个数减一(两个本不联通的块合并);
最后,我们倒序输出;
考虑到,每有一个摧毁的点,我们需要遍历它的出边;
由于是无向图,实际最多每条边要遍历两次;
所以总复杂度为O(mlogn);
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN=2000001;
int fa[MAXN],sc[MAXN],ans[MAXN],fst[MAXN<<1],nxt[MAXN<<1];
int n,m,q,cnt,tot,num;
struct hh
{
int from,to;
}ma[MAXN],ss[MAXN];
bool vis[MAXN];
int find(int x)
{
int r=x,t;
while(r!=fa[r]) r=fa[r];
while(x!=r) t=fa[x],fa[x]=r,x=t;
return r;
}
void build(int f,int t)
{
num++;
ss[num]=(hh){f,t};
nxt[num]=fst[f];
fst[f]=num;
return;
}
void solve()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++) fa[i]=i;//初始化;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&ma[i].from,&ma[i].to);
build(ma[i].from,ma[i].to);
build(ma[i].to,ma[i].from);
int fx=find(ma[i].from),fy=find(ma[i].to);
if(fx!=fy)
fa[fx]=fy;
}
scanf("%d",&q);
for(int i=1;i<=q;i++) scanf("%d",&sc[i]),vis[sc[i]]=1;
for(int i=0;i<n;i++) fa[i]=i;//初始化;
for(int j=1;j<=m;j++)
{
int x=ma[j].from,y=ma[j].to;
if(vis[x] || vis[y]) continue;
else
{
int fx=find(x),fy=find(y);
if(fx!=fy)
fa[fx]=fy;
}
}
cnt=0;
for(int i=0;i<n;i++) if(!vis[i] && fa[i]==i) cnt++;
num=ans[++tot]=cnt;
for(int i=q;i>=1;i--)
{
int x=sc[i];
// cout<<x<<" *)*)*)"<<endl;
num++;
vis[sc[i]]=0;
for(int j=fst[x];j;j=nxt[j])
{
int y=ss[j].to;
if(vis[y]) continue;
int fx=find(x),fy=find(y);
if(fx!=fy)
num--,fa[fx]=fy;
// cout<<num<<"*****"<<endl;
}
ans[++tot]=num;
// cout<<num<<"*(*(*("<<endl;
}
for(int i=tot;i>=1;i--) printf("%d\n",ans[i]);
}
int main()
{
solve();
return 0;
}