LCA的類型多種多樣,只說我知道的,就有倍增求LCA,tarjin求LCA和樹鏈剖分求LCA,當然,也還有很多其他的方法。
其中最常用,速度最快的莫過於樹鏈剖分的LCA了。
樹鏈剖分,首先字面理解一下,什么是樹鏈剖分。
就是把一棵樹剖分為若干條鏈,然后利用數據結構(樹狀數組,SBT,Splay,線段樹等等)去維護每一
條鏈,復雜度為O(logn)
那么,樹鏈剖分的第一步當然是對整棵樹進行遍歷,預處理一些要用的變量。
void dfs(int now){ siz[now]=1; deep[now]=deep[dad[now]]+1; for(int i=head[now];i;i=net[i]) if(to[i]!=dad[now]){ dad[to[i]]=now; dfs(to[i]); siz[now]+=siz[to[i]]; } }
其中dad[i]=j表示節點i的爸爸是j,deep[i]表示節點i的深度,siz[i]表示以節點i為祖先的子樹的大小(也就是這個子樹里節點的個數)
第二步就是對樹進行輕重邊的划分,這樣我們就可以保證每一個點屬於且只屬於一條鏈。
在這棵樹中其實,只有重鏈是我們用的到的,輕鏈一般就被忽略了。
而 我們一般是通過兒子個數的多少來划分出輕重邊的,在所有的兒子結點中,各以他們為祖先的子樹的節點的個數多的兒子節點與其父親的連邊就是一條重邊。這句話有點繞口,看圖理解一下:(因為這棵樹不一定是二叉樹,一個節點可能有多個兒子,這這例子是一個二叉樹的,不太全面。)
在這個圖里面6和9的連邊就是一條重邊,而6和7的連邊就是一條輕邊。類似的,9這個節點有兩個兒子,以這兩個兒子為祖先的子樹的大小是相同的,所以隨便找一條
邊作為重邊就可以了,而7只有一個子節點所以7和8的鏈理所因當就是重鏈了。
對於這樣的一顆樹來說,他的輕重鏈分布如下所示:其中,灰色粗線表示重邊,黑色細線表示輕邊。
這是他的邊的分布情況:
13
1 2
2 3
3 4
1 5
5 6
6 7
7 8
6 9
9 10
9 11
5 12
12 13
這是找輕重鏈的代碼:其中top[i]表示點i沿他的重邊上去,最高能到達的點。
比如上面的圖里面每個點的top分別是:1,2,2,2,1,1,7,7,1,10,1,12,12
void dfs1(int x){ int t=0; if(!top[x]) top[x]=x; for(int i=head[x];i;i=net[i]) if(to[i]!=dad[x]&&siz[to[i]]>siz[t]) t=to[i]; if(t){ top[t]=top[x]; dfs1(t); } for(int i=head[x];i;i=net[i]) if(to[i]!=dad[x]&&t!=to[i]) dfs1(to[i]); }
找完輕重鏈后,就可以進行下一步操作了,找最近公共祖先。
先看一下代碼:
int lca(int x,int y){ for(;top[x]!=top[y];){ if(deep[top[x]]<deep[top[y]]) swap(x,y); x=dad[top[x]]; } if(deep[x]>deep[y]) swap(x,y); return x; }
代碼是非常好理解的,就是一個while語句,當兩個點位於一條重鏈上的時候結束操作。
否則的話,深度深的點,順着他所在的重鏈向上跳。跳到這條重鏈的上一個點的位置。在兩個點位於一條鏈上之后,深度淺的點的位置,就是我們要找的公共祖先。
還是這個圖,如果要求8和11的最近公共祖先。第一步就是讓8順着邊向上跳他會跳到為位置6的點上去,然后6和11位於同一條鏈上,6的深度淺,6就是我們要求的最近公共祖先。
再舉一個復雜點的例子:如果要求10和13的最近公共祖先要怎么呢?
首先,找到兩個點中深度較深的點,是10,10號點向上跳,跳到9號點,然后是13號點向上跳,回跳到5號點,這是5和9位於同一條鏈上,5號點就是我們所求的最近公共祖先。
有人可能會產生這樣的問題:我在跳邊的時候,為什么要跳到重鏈頂點的父親,而不跳到重鏈頂點,這樣萬一錯過了最近公共祖先腫么辦?
那就要請你自己好好考慮了,因為這個問題在上面已經回答過了。

第一個問題:如果跳到頂點的話,像是上面的圖中的10號點就跳不動了,那公共祖先就出不來了。
第二個問題:不會。因為除了葉子結點,每一個節點都一定屬於一條重鏈。
題目描述
如題,給定一棵有根多叉樹,請求出指定兩個點直接最近的公共祖先。
輸入輸出格式
輸入格式:
第一行包含三個正整數N、M、S,分別表示樹的結點個數、詢問的個數和樹根結點的序號。
接下來N-1行每行包含兩個正整數x、y,表示x結點和y結點之間有一條直接連接的邊(數據保證可以構成樹)。
接下來M行每行包含兩個正整數a、b,表示詢問a結點和b結點的最近公共祖先。
輸出格式:
輸出包含M行,每行包含一個正整數,依次為每一個詢問的結果。
輸入輸出樣例
說明
時空限制:1000ms,128M
數據規模:
對於30%的數據:N<=10,M<=10
對於70%的數據:N<=10000,M<=10000
對於100%的數據:N<=500000,M<=500000
樣例說明:
該樹結構如下:
第一次詢問:2、4的最近公共祖先,故為4。
第二次詢問:3、2的最近公共祖先,故為4。
第三次詢問:3、5的最近公共祖先,故為1。
第四次詢問:1、2的最近公共祖先,故為4。
第五次詢問:4、5的最近公共祖先,故為4。
故輸出依次為4、4、1、4、4。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define MAXN 500010 using namespace std; int n,m,s,tot; int to[MAXN*2],net[MAXN*2],head[MAXN]; int dad[MAXN],deep[MAXN],siz[MAXN],top[MAXN]; void add(int u,int v){ to[++tot]=v;net[tot]=head[u];head[u]=tot; to[++tot]=u;net[tot]=head[v];head[v]=tot; } void dfs(int now){ siz[now]=1; deep[now]=deep[dad[now]]+1; for(int i=head[now];i;i=net[i]) if(to[i]!=dad[now]){ dad[to[i]]=now; dfs(to[i]); siz[now]+=siz[to[i]]; } } void dfs1(int x){ int t=0; if(!top[x]) top[x]=x; for(int i=head[x];i;i=net[i]) if(to[i]!=dad[x]&&siz[to[i]]>siz[t]) t=to[i]; if(t){ top[t]=top[x]; dfs1(t); } for(int i=head[x];i;i=net[i]) if(to[i]!=dad[x]&&t!=to[i]) dfs1(to[i]); } int lca(int x,int y){ for(;top[x]!=top[y];){ if(deep[top[x]]<deep[top[y]]) swap(x,y); x=dad[top[x]]; } if(deep[x]>deep[y]) swap(x,y); return x; } int main(){ scanf("%d%d%d",&n,&m,&s); for(int i=1;i<n;i++){ int u,v; scanf("%d%d",&u,&v); add(u,v); } dfs(s); dfs1(s); for(int i=1;i<=n;i++) cout<<top[i]<<" "; for(int i=1;i<=m;i++){ int u,v; scanf("%d%d",&u,&v); cout<<lca(u,v)<<endl; } } /* 13 1 1 1 2 2 3 3 4 1 5 5 6 6 7 7 8 6 9 9 10 9 11 5 12 12 13 */
練習題: