题目:http://www.lydsy.com/JudgeOnline/problem.php?id=1015
意思是给一个图,每次删除一个点和与其相关的连边,每次求出连通块的个数。
这道题按照题目的说法,很不好想。但是如果把题目倒过来,我们只需要每次在图中添加节点,并判断添加的节点是否:{产生新的连通块,将两个已有连通块连在一起}即可。
这就是并查集的思想。
首先我们建一张参照图,建立题中的所有边。
用ans表示现在的连通块个数,初始为0即可。
然后统计出那些点没有被踢出(用ove来表示),建立一张新的图。统计这个图的联通块数量,存为ans,存入最终输出的答案数组中。
逆序添加点,每次添加,更新ans,存入最后输出的答案数组中。
最后逆序输出答案数组即可。
[更新操作]insert
首先,此点产生了一个新的连通块,ans++. (*)
我们加入了一个点。首先要按照原图,寻找相连的边,如果此边通向的点在图中,并且与加入的点不在一个连通块(用并查集即可),那么ans--,并连接这两个连通块。
不在一个连通块的话就不用管了。(因为已经ans++了。(*处))
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=400010;
int fa[maxn],head[maxn],cnt,d[maxn],gnt;
bool ove[maxn],use[maxn];
int n,m,k,sum[maxn],ans;
struct edge
{
int nxt,to;
}e[maxn],g[maxn];
void add1(int x,int y)//对照组(所有边)
{
e[++cnt].to=y;
e[cnt].nxt=head[x];
head[x]=cnt;
}
void add2(int x,int y)//正式组(被攻打之后)
{
g[++gnt].to=y;
g[gnt].nxt=head[x];
head[x]=gnt;
}
int find(int x)
{
if(fa[x]==x)return x;
return fa[x]=find(fa[x]);
}
void insert(int x)
{
for(int i=head[x];i;i=e[i].nxt)
if(use[e[i].to]==1)//在图中
{
add2(x,e[i].to);
if(find(e[i].to)!=find(x))
{
ans--;
fa[find(x)]=find(e[i].to);
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)fa[i]=i;
int x,y;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
x++,y++;
add1(x,y);
add1(y,x);
}
cin>>k;
for(int i=1;i<=k;i++)
{
scanf("%d",&d[i]);
d[i]++;
ove[d[i]]=1;
}
for(int i=1;i<=n;i++)
if(ove[i]==0)
{
insert(i);
ans++;
use[i]=1;
}
sum[k+1]=ans;
for(int i=k;i>=1;i--)
{
insert(d[i]);
ans++;
use[d[i]]=1;
sum[i]=ans;
}
for(int i=1;i<=k+1;i++)printf("%d\n",sum[i]);
return 0;
}