倍增求lca

倍增求lca 模板
求最近公共祖先lca(Least Common Ancestors),什麼是公共祖先,給定一棵樹,若節點z即使節點x的祖先,也是節點y的祖先,則稱z是x,y的公共祖先,在x,y的所有公共祖先中,深度最大的節點稱x,y的最近公共祖先,記做LCA(x,y)。
舉個例子
在這裏插入圖片描述
LCA(4,5) = 2,LCA(5,6)=1,LCA(2,3)=1;
那麼如何求LCA
先給出一張我們接下來要講的例子:
例子
第一:暴力求法
先暴力dfs出每個點的深度,假設以 171717和 18 爲例,既然要求LCA,那麼我們就讓他們一個一個向上跳,直到相遇爲止。第一次相遇即是他們的LCA。 模擬一下就是:
17−>14−>10−>7−>3
18−>16−>12−>8−>5−>3
最終結果就是 3
但是這個算法的時間複雜度卻是很高,所以我們採取了第二種重要的方法。
樹上倍增法
設pre[x][k]表示x的2k 輩祖先,即從x向根節點走2k步到達的節點,若該節點不存在,則令pre[x][k] = 0,f[x][0]就是x的父節點。
所謂倍增,就是按2的倍數來增大,也就是跳 1,2,4,8,16,32 …… 不過在這我們不是按從小到大跳,而是從大向小跳,即按……32,16,8,4,2,1來跳,如果大的跳不過去,再把它調小。這是因爲從小開始跳,可能會出現“悔棋”的現象。拿 5爲例,從小向跳,5≠1+2+4,所以我們還要回溯一步,然後才能得出5=1+4;而從大向小跳,直接可以得出5=4+1。這也可以拿二進制爲例,5(101),從高位向低位填很簡單,如果填了這位之後比原數大了,那我就不填,這個過程是很好操作的。
還是以 17 和 18 爲例(此例只演示倍增,並不是倍增LCA算法的真正路徑):
17−>3
18−>5−>3
算法時間度爲O(logn)。
接下來我們拆分算法步驟:
1:設depth[x]表示x的深度,不妨設depth[x]>=depth[y],否則交換x,y;
2:用上述二進制拆分思想,把x向上調整到與y同一高度。
3:若此時x==y,說明lca(x,y)就是x.
4:若此時x!=y,那麼繼續保持二進制拆分思想,把x,y同時向上調整,並保持深度一致並且二者不會相會。
/我們在跳的時候不能直接跳到它們的LCA,因爲這可能會誤判,比如4和8,在跳的時候,我們可能會認爲1是它們的LCA,但1只是它們的祖先,它們的LCA其實是3。所以我們要跳到它們LCA的下面一層,比如4和8,我們就跳到4和5,然後輸出它們的父節點,這樣就不會誤判了。/
5:此時x,y必定只差一步就會相會了,它們的父節點pre[x][0]就是我們要求的lca。

完整的求17和18的LCA的路徑:
17−>10−>7−>3
18−>16−>8−>5−>3
解釋:首先,18要跳到和17深度相同,然後18和17一起向上跳,一直跳到LCA的下一層(17是7,18是5),此時LCA就是它們的父親

在算法實現的過程中,我們可以加上log2(x)log_2(x)的常數時間的優化。
下面給出各個步驟代碼。
預處理:

void dfs(int f,int fa){
	depth[f] =  depth[fa] + 1;
	pre[f][0] = fa;
	for(int i=1;(1<<i) <= depth[f];i++)
		pre[f][i] = pre[pre[f][i-1]][i-1];//這個轉移可以說是算法的核心之一
                                //意思是f的2^i祖先等於f的2^(i-1)祖先的2^(i-1)祖先
                                //2^i=2^(i-1)+2^(i-1)
	for(int i=head[f];i;i=nex[i]){
		if(ver[i] != fa)
		dfs(ver[i],f);
	}
}

查詢x,y的lca:

int lca(int x,int y){
	if(depth[x] < depth[y]) swap(x,y); ////用數學語言來說就是:不妨設x的深度 >= y的深度
	while(depth[x] > depth[y])
	x = pre[x][lg[depth[x] - depth[y]]-1]; ////先跳到同一深度
	
	if(x==y) return x;//如果x是y的祖先,那他們的LCA肯定就是x了
	
	for(int i=lg[depth[x]]-1;i>=0;i--)  //不斷向上跳(lg就是之前說的常數優化)
	if(pre[x][i]!=pre[y][i])  //因爲我們要跳到它們LCA的下面一層,所以它們肯定不相等,如果不相等就跳過
	x = pre[x][i],y=pre[y][i];
	
	return pre[x][0]; 
}

給出測試鏈接:https://www.luogu.org/problem/P3379

給出模板:

#include<bits/stdc++.h>
using namespace std;
const int N = 5e5+7,M = 1e6+7;
int n,m,s,cnt;
int pre[N][22],depth[N],lg[N];
int head[M],ver[M],nex[M];
void add(int x,int y){
	ver[++cnt] = y;
	nex[cnt] = head[x];
	head[x] = cnt;
} 
void read(){
	scanf("%d%d%d",&n,&m,&s);
	for(int i=1,x,y;i<n;i++){
		scanf("%d%d",&x,&y);
		add(x,y);add(y,x);
	}
}
void dfs(int f,int fa){
	depth[f] =  depth[fa] + 1;
	pre[f][0] = fa;
	for(int i=1;(1<<i) <= depth[f];i++)
		pre[f][i] = pre[pre[f][i-1]][i-1];//這個轉移可以說是算法的核心之一
                                //意思是f的2^i祖先等於f的2^(i-1)祖先的2^(i-1)祖先
                                //2^i=2^(i-1)+2^(i-1)
	for(int i=head[f];i;i=nex[i]){
		if(ver[i] != fa)
		dfs(ver[i],f);
	}
}
int lca(int x,int y){
	if(depth[x] < depth[y]) swap(x,y); ////用數學語言來說就是:不妨設x的深度 >= y的深度
	while(depth[x] > depth[y])
	x = pre[x][lg[depth[x] - depth[y]]-1]; ////先跳到同一深度
	
	if(x==y) return x;//如果x是y的祖先,那他們的LCA肯定就是x了
	
	for(int i=lg[depth[x]]-1;i>=0;i--)  //不斷向上跳(lg就是之前說的常數優化)
	if(pre[x][i]!=pre[y][i])  //因爲我們要跳到它們LCA的下面一層,所以它們肯定不相等,如果不相等就跳過
	x = pre[x][i],y=pre[y][i];
	
	return pre[x][0]; 
}
void solve(){
	dfs(s,0);
	for(int i=1;i<=n;i++) ////預先算出log_2(i)+1的值,用的時候直接調用就可以了
	lg[i] = lg[i-1]+(1<<lg[i-1]==i);
	
	for(int i=1,x,y;i<=m;i++){
		scanf("%d%d",&x,&y);
		printf("%d\n",lca(x,y));
	}
}
int main(){
	read();
	solve();
	return 0;
}

資料參考:
https://www.luogu.org/blog/morslin/#
李煜東 算法競賽進階指南

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章