游戏「并查集」
游戏「并查集」
题目描述
Mirko和 Slavko 爱玩弹球戏。在一个令人激动的星期五,Mirko 和 Slavko 玩了一把弹球游戏。Mirko 构建一个有向图,所有顶点最多有 1 条出边。弹球从 1个顶点出发可以沿着一条边移动到它的邻接点,只要它存在,而且它会继续移动到后者的邻接点去,直到最后到达一个找不到出边的顶点才停下来。如果不存在这样的点,弹球可能无限运动下去。
为了确信 Slavko理解游戏的规则,Mirko 将发起一系列询问,询问的类型如下:
1 X:除非弹球陷入循环,弹球从 X出发,最终将在哪个点停下来。
2 X:删除 X的出边(保证该边总是存在)
注意:询问是按顺序执行的。
输入
第一行包含一个正整数 N
(1
<=N
<=300000
),表示图的定点数。
第二行包含由空格隔开 N
个正整数,第 i
个数表示从 i
顶点可以通过出边到达的定点编号。0
表示该点没有出边。
接下来的一行包含 1
个整数 Q
(1
<=Q
<=300000
),表示询问的次数。格式如上所示。
输出
对于第 1类询问,输出弹球停止时所在顶点编号,每行 1 个,按照查询的顺序输出。如果弹球无法停止,则输出 CIKLUS
样例输入
2 3 1
7
1 1
1 2
2 1
1 2
1 1
2 2
1 2
样例输出
CIKLUS
1
1
2
思路分析
- 因为每个节点都只有一条出边,所以我们可以考虑并查集,并查集的祖先节点即为断点。
- 针对有环的情况,我们只需要记录一下跑的次数,若大于n则说明存在环
- 这题的关键在于删边这里,我们使用倒序并查集(离线操作),另开一个数组记录删边前的状态,逐步复原即可
代码
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn = 3e5+10;
int f[maxn],ff[maxn],a[maxn][2],n; //ff数组记录删边前的状态
int find(int x,int Clock){
if(Clock>n)return f[x] = 0; //有环
if(x==f[x])return x;
return f[x] = find(f[x],Clock+1);
}
int main(){
scanf("%d",&n);
for(int i = 1;i <= n;i++)f[i] = i;
for(int i = 1;i <= n;i++){
int x;scanf("%d",&x);
if(x)f[i] = ff[i] = x;
}
int q;scanf("%d",&q);
for(int i = 1;i <= q;i++){
scanf("%d%d",&a[i][0],&a[i][1]); //操作种类和对象一并记录
if(a[i][0]==2){
f[a[i][1]] = a[i][1];
}
}
for(int i = q;i >= 1;i--){ //倒序处理,保证前面的删边操作不会对前面的查询造成影响
if(a[i][0]==1){
a[i][1] = find(a[i][1],0); //正常查询
}
else f[a[i][1]] = ff[a[i][1]]; //复原
}
for(int i = 1;i <= q;i++){
if(a[i][0]==1){
if(a[i][1])printf("%d\n",a[i][1]);
else printf("CIKLUS\n"); //a[i][1]为0说明有环
}
}
return 0;
}