游戏「并查集」

游戏「并查集」

题目描述

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
",a[i][1]);
			else printf("CIKLUS
"); //a[i][1]为0说明有环
		}
	}
	return 0;
}
原文地址:https://www.cnblogs.com/hhhhalo/p/13324391.html