星球大战(并查集逆用)
题目描述:
很久以前,在一个遥远的星系,一个黑暗的帝国靠着它的超级武器统治着整个星系。某一天,凭着一个偶然的机遇,一支反抗军摧毁了帝国的超级武器,并攻下了星系中几乎所有的星球。这些星球通过特殊的以太隧道互相直接或间接地连接。但好景不长,很快帝国又重新造出了他的超级武器。凭借这超级武器的力量,帝国开始有计划地摧毁反抗军占领的星球。由于星球的不断被摧毁,两个星球之间的通讯通道也开始不可靠起来。
现在,反抗军首领交给你一个任务:给出原来两个星球之间的以太隧道连通情况以及帝国打击的星球顺序,以尽量快的速度求出每一次打击之后反抗军占据的星球的连通块的个数。(如果两个星球可以通过现存的以太通道直接或间接地连通,则这两个星球在同一个连通块中)。
输入描述:
输入第一行包含两个整数,n,m,分别表示星球的数目和以太隧道的数目,星球用0~n-1的整数编号。
接下来的m行,每行包括两个整数x,y,表示星球x和星球y之间有“以太”隧道,可以直接通讯。
接下来的一行为一个整数k,表示将遭受攻击的星球的数目。
接下来的k行,每行有一个整数,按照顺序列出了帝国军的攻击目标。这k个数互不相同,且都在0到n-1的范围内。 其中 1 <= m <= 2*10^5,1 <= n <= 2m,x != y。
输出描述:
第一行是开始时星球的连通块个数。接下来的k行,每行一个整数,表示经过该次打击后现存的星球的连通块个数。
输入样例:
8 13
0 1
1 6
6 5
5 0
0 6
1 2
2 3
3 4
4 5
7 1
7 2
7 6
3 6
5
1
6
3
5
7
输出样例:
1
1
1
2
3
3
思路:
这道题依然是用并查集,但是并查集并不支持对于已经合并的数据在拆分掉,也就是说,如果我们把所有的星球都通过并查集合并在一起了,在把一个星球给摧毁了,在合并的关系中去掉这一个星球,并查集时做不到的。所以我们要倒着用并查集,将所有的数据都先输入存起来,将两个有关系的星球用邻接表存起来,方便找关系。将要摧毁的星球存入数组a中,并在book数组中标记这个星球,然后计算除了被摧毁星球外的所有星球一共组成多少个连通块,这就是题目中当要被摧毁的星球摧毁后的连通块数量。所以我们是反着求的,将这个值存入数组的最后一个位置,然后将要被摧毁的星球一个一个的重建,计算每重建一个星球后的连通块个数,这就和一个一个摧毁星球后计算连通块个数相反,然后把数组输出就行了。
代码:
#include<stdio.h>
#define N 400010
int f[N]; //用于存放并查集的数组
int a[N]; //存放要被摧毁的星球
int book[N]; //标记要被摧毁的星球
int n,m;
int len=0; //控制结构体数组的计数变量
struct note
{ //邻接表的结构体
int x;
int y;
int head;
int next;
}que[N];
int init()
{ //并查集的初始化
for(int i=0;i<n;i++)
{
f[i]=i;
que[i].head=-1; //将邻接表赋为 -1
}
}
int add(int x,int y)
{ //将无向图用邻接表储存起来
len++;
que[len].x=x;
que[len].y=y;
//创建邻接表的两步
que[len].next=que[x].head;
que[x].head=len;
}
int find(int v)
{ //并查集的查找
if(f[v]!=v)
f[v]=find(f[v]);
return f[v];
}
void merge(int tx,int ty)
{ //并查集的合并
int t1,t2;
t1=find(tx);
t2=find(ty);
if(t1!=t2)
f[t1]=t2;
return ;
}
int main()
{
int x,y,k,num=0;
int ans[N]; //用于存放每次摧毁星球后的连通块个数
//读入数据
scanf("%d %d",&n,&m);
init(); //初始化
for(int i=1;i<=m;i++)
{
scanf("%d %d",&x,&y);
add(x,y); //因为是无向图
add(y,x); //所以要用邻接表存两次
}
scanf("%d",&k);
for(int i=1;i<=k;i++)
{
scanf("%d",&a[i]); //将摧毁的点输入进数组
book[a[i]]=1; //标记摧毁的点
}
//将除了要被摧毁的星球外的其他星球组成连通块
for(int i=1;i<=len;i++)
if(book[que[i].x]==0&&book[que[i].y]==0)
merge(que[i].x,que[i].y);
//计算除了要被摧毁的星球外其他的星球能组成几个连通块
for(int i=0;i<n;i++)
if(book[i]==0&&f[i]==i)
num++;
ans[k+1]=num; //存入数组
for(int i=k;i>=1;i--)
{ //将被摧毁的星球重建
int u=a[i];
num++; //在被摧毁的星球还未连接其他星球时算一个连通块
book[u]=0; //取消标记
for(int j=que[u].head;j!=-1;j=que[j].next)
{ //在邻接表中找到与要重建的星球有关联的星球
int v=que[j].y;
if(book[v]==0&&f[find(u)]!=f[find(v)])
{ //将星球合并
merge(u,v);
num--; //因为与其他星球合并,所以连通块减少一个
}
}
ans[i]=num; //将重建后的连通块个数存入数组
}
for(int i=1;i<=k+1;i++)
printf("%d\n",ans[i]); //输出每次摧毁星球后的连通块个数
return 0;
}