算法
并查集+逆序
思路
做这道题前呢,我们先出门左转关闭农场,一道类似的更简单一丢丢的题
然后,我们考虑一下这题,因为并没有过多的操作,只是要我们求一下连通块的个数而已(也就是连通性,具有传递性的连通),而这恰好是并查集所擅长的。
然而,我们正向看题目时就会发现不支持删除操作的并查集似乎办不到。但,如果我们把删除操作换成加入操作,就可以办到了。这也就是逆向思维。题目支持离线,所以我们可以逆向处理出所有答案以后再全部输出就好了。
考虑最后的连通状态:标记摧毁的点,把不摧毁的点之间能连的边连起来,同时统计连通块的数量即为所求
中间的连通状态:于最后的连通状态处理方法类似,从后往前依次加入被删除的点,枚举出边,连通。统计连通块的数量即为所求
倒序存,正序输shu
#include<bits/stdc++.h>
using namespace std;
int now[400010],tot;
int team[400010];
bool flag[400010];//是否存在
int father[400010];
int n,m,k;
int ans[400010];
int sum;
struct node
{
int from;
int to;
int next;
}a[400010];//邻接表存图
void put(int x,int y)
{
a[tot].from=x;
a[tot].next=now[x];
a[tot].to=y;
now[x]=tot;
tot++;
}
int find(int x)
{
if(x!=father[x])father[x]=find(father[x]);
return father[x];
}//并查集基本操作
void unionn(int x,int y)
{
x=find(x),y=find(y);
if(x==y)return;
sum--;//合并集合减一
father[x]=y;
}//同上
int main()
{
scanf("%d%d",&n,&m);
int x,y;
for(int i=0;i<n;i++)
father[i]=i,now[i]=-1;//初始化
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
put(x,y);
put(y,x);//存图
}
scanf("%d",&k);
sum=n-k;//假设剩下的都不联通
for(int i=1;i<=k;i++)
{
scanf("%d",&x);
flag[x]=true;
team[i]=x;
}
for(int i=0;i<2*m;i++)
{
if(!flag[a[i].from]&&!flag[a[i].to])
{
unionn(a[i].from,a[i].to);//链接所有边
}
}
//printf("k:%d
",k);
ans[k+1]=sum;
for(int i=k;i>=1;i--)
{
int u=team[i];
sum++;//新增一个点+1连通块
flag[u]=false;
for(int i=now[u];i!=-1;i=a[i].next)
{
if(!flag[a[i].from]&&!flag[a[i].to])//都在并查集内(没被摧毁)
{
unionn(a[i].from,a[i].to);
}
}
ans[i]=sum;
}//倒着做
// printf("k:%d
",k);
for(int i=1;i<=k+1;i++)
printf("%d
",ans[i]);
}