1. 程式人生 > 其它 >金牌導航 啟發式合併-連通性詢問 題解

金牌導航 啟發式合併-連通性詢問 題解

題面:


注意本題強制線上

做法詳解:

一看到每一次加一條邊,詢問某兩個點的聯通的就自然而然地能想到使用並查集。
我們考慮最暴力的做法:
每一次從第一條邊開始依次加邊,直到某次加邊之後 \(u,v\) 聯通
顯然這樣做是不可以的,很明顯我們不可能每一次都從頭加一遍邊,所以就考慮這樣直接來一條邊加一條然後維護一個其他的資訊。
我們考慮並查集使用按秩合併,對於一個合併 \(y\) 合併到 \(x\) 裡,我們考慮維護一個數組 val[y] 代表直接合並 \(y\) 地這一個合併是第幾次合併,也就是第幾條加入的邊,因為我們的每一個點會且僅會被當作子樹直接合並一次,所以這個陣列的值也是確定的。這樣就考慮對於每一組詢問該如何去回答.
我們能發現一個很奇妙的性質,對於使 \(u,v\)

聯通有貢獻的邊,一定在且僅在並查集中它們之間的唯一路徑上,也就是隻有這些邊對他們的聯通有幫助,其他的邊無論存在與否都是無用的(仔細思考一下,最重要的一點),那麼答案也就是這些對它們的聯通有幫助的這些邊的編號的最大值,因為只有這些邊都被選擇 \(u,v\) 才聯通,所以答案也就是這些邊中最後一個被加入的邊
現在的問題就是如何去找它們在並查集上的唯一路徑,這個時候其實我們的按秩合併的作用就體現出來了,我們能發現按秩合併的並查集,**任何一條從根出發的鏈的長度都不會超過 \(log_{2}n\) **,所以我們可以直接從這兩個點暴力向上跳,然後將經過的邊取最大值就好了.暴力向上跳還是有一些細節的,具體見程式碼.
時間複雜度: \(O(mlog_{2}n)\)

AC程式碼:

#include<bits/stdc++.h>
using namespace std;
const long long MAXN = 1e6+5;
long long tot,n,m,fa[MAXN],height[MAXN],val[MAXN],last_ans = 0;
long long find_fa(long long x){
	while(fa[x] != x)
		x = fa[x];
	return x;
}
void merge(long long x,long long y,long long num){
	x = find_fa(x), y = find_fa(y);  //按秩合併 
	if(x == y)	return;
	if(height[x] < height[y])	swap(x,y);
	fa[y] = x;
	val[y] = num;
	if(height[x] == height[y])
		height[x]++;
}
long long get_ans(long long x,long long y){
	long long fa_x = find_fa(x),fa_y = find_fa(y);
	if(fa_x != fa_y)
		return 0;
	else{
		long long cnt_x = 0,cnt_y = 0,ans = 0;
		fa_x = x,fa_y = y;
		while(fa[fa_x]!=fa_x){
			fa_x = fa[fa_x];
			cnt_x++;
		}
		while(fa[fa_y]!=fa_y){
			fa_y = fa[fa_y];
			cnt_y++;
		}
		if(cnt_x < cnt_y){
			swap(x,y);
			swap(cnt_x,cnt_y);
		}
		for(long long i=1; i<=cnt_x - cnt_y; i++){   //跳到同一深度 
			ans = max(ans,val[x]);
			x = fa[x];
		}
		while(x != y){   //個人碼風習慣這裡寫 fa[x] != fa[y] ,然後下面統計一下 ans = max(val[x],val[y],ans) ,但是因為可能 x 或 y 就是根所以直接跳上面就會出大問題 
			ans = max(ans,val[x]);
		        ans = max(ans,val[y]);
			x = fa[x];y = fa[y];
		}
		return ans;
	}
}
int main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	memset(val,0x3f,sizeof(val));
	cin>>n>>m;
	for(long long i=1; i<=n; i++){
		fa[i] = i;
		height[i] = 1;
	}
	for(long long i=1; i<=m; i++){
		long long from,to,opt;
		cin>>opt>>from>>to;
		from^=last_ans;to^=last_ans;
		if(opt == 0){
			merge(from,to,++tot);
//			printf("%d %d\n",from,to);
		}
		else if(opt == 1){
			last_ans = get_ans(from,to);
			printf("%lld\n",last_ans);
		}
	}
	return 0;
}