1. 程式人生 > 實用技巧 >遊戲「並查集」

遊戲「並查集」

遊戲「並查集」

題目描述

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;
}