题目传送门:https://www.luogu.org/problemnew/show/P1197
题意:
有n个点,m条边,现在有q个操作,每次都会删掉一个点,求每次删完点后还有多少个联通块。
思路:
反向并查集(一眼看出,这都是这种题的套路)。
我们先将要删掉的点都删掉,看看有多少个联通分块剩下(设有x个),剩下的联通分块就为经过打击后的联通分块的个数。
设ans[i]表示经过第i个操作后剩下的联通分块的个数,则ans[q+1]=x。
我们就反向建点建边,遇到一个被删掉的点,就将它所连出的边加紧同一个联通分块,不断更新结果即可。
套路题,没什么好讲的。
代码:
#include<cstdio>
int n,m,q,len=0;
int last[400010],p[400010],fa[400010],ans[400010];
struct node{int x,y,next;} a[1000010];
bool bz[400010];
void ins(int x,int y)
{
a[++len].x=x;a[len].y=y;a[len].next=last[x];last[x]=len;
}
int find(int x)
{
return x==fa[x]?x:fa[x]=find(fa[x]);
}
int main()
{
int x,y;
scanf("%d %d",&n,&m);
for(int i=0;i<=n;i++)
last[i]=-1,fa[i]=i;
for(int i=1;i<=m;i++)
{
scanf("%d %d",&x,&y);
ins(x,y),ins(y,x);
}
scanf("%d",&q);
for(int i=1;i<=q;i++)
{
scanf("%d",&p[i]);
bz[p[i]]=true;
}
int tot=n-q;
for(int i=1;i<=m<<1;i++)
{
int x=a[i].x,y=a[i].y,t1=find(x),t2=find(y);
if(!bz[x]&&!bz[y]&&t1!=t2)
{
tot--;
fa[t1]=t2;
}
}
ans[q+1]=tot;
for(int k=q;k>=1;k--)
{
int x=p[k];
bz[x]=false;
tot++;
for(int i=last[x];i!=-1;i=a[i].next)
{
int t1=find(x),y=a[i].y,t2=find(y);
if(!bz[y]&&t1!=t2)
{
tot--;
fa[t1]=t2;
}
}
ans[k]=tot;
}
for(int i=1;i<=q+1;i++)
printf("%d\n",ans[i]);
}