LCA 最近公共祖先
LCA(不知道全稱叫什麼)
-倍增版-
作用:在一棵樹中求兩點公共祖先中深度最大的那個
存在一顆 n 點的樹,詢問 m 次兩個點的最近公共祖先
複雜度:預處理O(nlogn),詢問O(logn)
主要思想:倍增
預處理:
預處理出 f [ i ] [ j ] 陣列,表示 i 點向上 2j步的點,即自己往上數第 2j個祖先
預處理出 dep [ i ] 陣列,表示以第 i 個點的深度
這樣先給出一張以1為根的樣例樹,比較好解釋一點,這裡的值都是相對於根為1的情況,預處理的全部內容以該樹為參考
那麼 f [ 12 ] [ 0 ] = 8 , f [ 12 ] [ 1 ] = 5 , f [ 12 ] [ 2 ] = 1
那麼 dep [ 1 ] = 1 , dep [ 5 ] = 3 , dep[ 13 ] = 5
i 點向上 2j相當於 i 點往上數的第 2j-1個祖先向上 2j-1步
預處理轉移方程:
for(int i=1;(1<<i)+1<=dep[u];++i) f[u][i]=f[f[u][i-1]][i-1];
dep [ ] 陣列的處理用dfs完成,於是在處理dep [ ] 的同時,可以順便處理 f [ ] [ ]陣列
那麼預處理只需要一個dfs即可完成。
注意很多時候給邊的時候未給定邊的方向(比如只說u點到v點有一條邊,沒說明是u到v或v到u),建邊要雙向,由一個根開始的dfs來確定每條邊的方向。
void dfs(int u,int fa){ v[u]=true; f[u][0]=fa; dep[u]=dep[fa]+1; for(int i=1;(1<<i)+1<=dep[u];++i) f[u][i]=f[f[u][i-1]][i-1]; for(int i=head[u];i;i=edge[i].nxt) if(!v[edge[i].to]) dfs(edge[i].to,u); } int main(){ dfs(1,0); }
注意dfs中 fa 是 u 的父親。
為什麼dfs的開始可以 0 是 1 的父親開始呢?
這樣預處理難道不會出現問題嗎?
還真沒有,將 0 定為 1 的父親後,由於第一個for中(1<<i)+1<=dep[u] 所以除了 1 的點全部不會將 0 作為父親
即使全把 0 做父親也沒有事情,因為最近公共祖先的定義保證了兩個點最大的公共最先頂多是根,不會到 0
查詢:
在講查詢之前先來說說倍增的思想
一個 x 個單位長的區間,可以分解為幾個不同的 2k的長度的小區間
如14可以拆成8+4+2
同樣的,我們想到從0到14可以拆解為如上三個步驟,先走 23步再走 22 步再走 21
相關知識可以證明這個步數的組成是不同的且唯一的 (請查閱小學奧數)
如此一來在一棵樹中,求一個點往上走 q 步到達的祖先只需要 logq 的複雜度
我們考慮 x,y兩點處在共同的深度時
他們有許多的公共祖先,若 f [ x ] [ k ]與f [ y ] [ k ] 相同,代表他們向上走 2k步的點是他們的公共祖先
如圖a 是x、y的最近公共祖先,a的祖先都是x、y的祖先,p、q是 a 兒子且dep[p]=dep[q]=dep[a]+1
LCA的大致思想是不斷更新x,y的位置,讓他們向上爬,p是我們希望x最終到達的位置,q是我們希望y最終到達的位置。
我們每次確定一個值k,表示讓x,y都爬 2k步,之前說了x到p與y到q的路徑可以拆分成不同的2k,所以讓k改變,判斷這次的k是否需要爬即可
可以發現如果走到a或a的祖先(x、y的公共祖先)時,x和y的位置變為相同,需要爬的部分在於a以下
int same_LCA(int x,int y){ int k=log2(dep[x])+1; while(k>=0){ if(f[x][k]!=f[y][k]) //本次爬完完在x~p和y~q的範圍內 x=f[x][k],y=f[y][k]; //爬 --k; }
//此時的x,y即為p,q的位置 }
那麼此時的fa[x][0]= p往上爬一步= a號點 =最近公共祖先
我們再考慮 x,y 不同深度
那麼簡單,把x,y調成同深度即可,原理與上面相同
if(dep[x]<dep[y]) swap(x,y); while(dep[x]>dep[y]){ int l=log2(dep[x]-dep[y]); x=f[x][l]; }
然後再貼個程式碼,模板題是[洛谷 P3379 【模板】最近公共祖先(LCA)]
#include<bits/stdc++.h> #define maxn 1000010 using namespace std; int n,m,s,edge_num; int head[maxn],f[maxn][21],father[maxn],dep[maxn]; bool v[maxn]; struct { int nxt,to; }edge[maxn]; void add_edge(int from,int to){ edge[++edge_num].nxt=head[from]; edge[edge_num].to=to; head[from]=edge_num; } void dfs(int u,int fa){ v[u]=true; f[u][0]=fa; dep[u]=dep[fa]+1; for(int i=1;(1<<i)+1<=dep[u];++i) f[u][i]=f[f[u][i-1]][i-1]; for(int i=head[u];i;i=edge[i].nxt) if(!v[edge[i].to]) dfs(edge[i].to,u); } int LCA(int x,int y){ if(dep[x]<dep[y]) swap(x,y); while(dep[x]>dep[y]){ int l=log2(dep[x]-dep[y]); x=f[x][l]; } if(x==y) return x; int k=log2(dep[x])+1; while(k>=0){ if(f[x][k]!=f[y][k]) x=f[x][k],y=f[y][k]; --k; } return f[x][0]; } int main(){ scanf("%d%d%d",&n,&m,&s); for(int i=1;i<=n-1;++i){ int u,v; scanf("%d%d",&u,&v); add_edge(u,v); add_edge(v,u); } dfs(s,0); for(int i=1;i<=m;++i){ int u,v; scanf("%d%d",&u,&v); printf("%d\n",LCA(u,v)); } }