题目大意:
n个点,m条边的无向图;k次操作,每次操作是删掉一个点e[i],求0~k次操作(k+1)次操作的过程中,图的联通块数量。
(k+1次:是因为,0次操作表示,一个点都没炸的时候,先要算一次全图的联通块数量)。
思路分析:
之前做过一道类似的蒸发学水,是在矩阵中完成基本相同的操作,这套题是拓展到了图上。
1、因为求联通块的数量,应该能想到是并查集(并查集就是一个两句话的函数,不懂请百度)
2、如果按题目思路的顺序来做的话,不知道怎么办(e xin);
3、逆序做的话,会变得浅显易懂。
4、初始化(算第一个情况的时候方法很多,本来我用深搜的,但是看了 hanks_o神的思路,用了更简单的方法)要注意一下。
5、题目的点是(0~n-1,我用1~n)其他的没有复杂的地方,代码有注解~
d
上代码:
#include<cstdio>
const int mx=400005;
struct nodx{int h,v;}a[mx];//点
struct nodb{int x,y,gg;}b[mx*2];//边
int n,m,f[mx],e[mx],su[mx],ans=0,len=0;
//f数组用于并查集
//e数组用来记录破坏的点的顺序;su数组记录答案
void ins(int x,int y)//邻接表
{ len++;b[len].x=x;b[len].y=y;b[len].gg=a[x].h;a[x].h=len; }
int ch(int x) //并查集
{ if(x==f[x]) return x; return f[x]=ch(f[x]);}
int main()
{
scanf("%d %d",&n,&m); int x,y,tx,ty;
for(int i=1;i<=n;i++) { f[i]=i;a[i].v=a[i].h=0; }//并查集的初始化是基础
for(int i=1;i<=m;i++)//构图
{
scanf("%d %d",&x,&y);
x++;y++;ins(x,y);ins(y,x);
}
scanf("%d",&m);//删点的个数
for(int i=1;i<=m;i++)
{
scanf("%d",&e[i]);e[i]++;a[e[i]].v=1;//删掉的点,v值为 1
}
//↓↓↓↓↓初始化部分
ans=n-m;//在没做任何操作之前,因为删掉了m个点,剩下(n-m)个集
for(int i=1;i<=len;i++)//扫描边,把剩下点合并,就算出来最后的集合数量
{
x=b[i].x;y=b[i].y;
if(a[x].v==0&&a[y].v==0)//点为被删除
{
tx=ch(x); ty=ch(y);
if(tx!=ty) //原来未连接
{
f[tx]=ty; ans--;//连接,少了一个集合,ans-1
}
}
}
//↑↑↑↑↑初始化部分
su[m+1]=ans;//(m+1)表示删掉m个点之后的状态
for(int k=m;k>=1;k--)//删掉 m-1~0个点的状态,注意是倒推的
{
a[e[k]].v=0; x=e[k]; tx=ch(x); ans++;
for(int i=a[x].h;i>0;i=b[i].gg)
{
int y=b[i].y;
if(a[y].v==0)
{
ty=ch(y);//并查集是一个类似离线的算法,每次查找都要更新
if(tx!=ty)
{
f[ty]=tx;ans--;//注意这里的挂靠方向
}//如果写成 f[tx]=ty; 是错的~~~~思考一下?
}
}
su[k]=ans;//每次存答案
}
for(int i=1;i<=m+1;i++) printf("%d\n",su[i]);//顺序输出
return 0;
}
sdf
sdf
sdf