百度百科關於LCA的解釋:LCA(Least Common Ancestors),即最近公共祖先,是指在有根樹中,找出某兩個結點u和v最近的公共祖先。(有多種變型例如求兩點間的距離如HDU2586,求最大公共的長度如CodeForces - 832D 等等)
題目: POJ 1984 HDU 2586 ZOJ 3195 POJ 1330 CodeForces - 832D
1.跳躍法/倍增LCA優化(在線算法)
倍增練習題:CodeForces - 932D (非LCA)
假設我們求兩節點的LCA,需要進行以下幾種操作:
1.優先處理出各個節點的深度;
2.判斷兩節點的深度是否相同;
3.如果不相同,對深度大的節點進行跳躍操作,直到兩點深度相同;
4.判斷當前兩節點所在的節點是否為同一節點,是則其為LCA,否則繼續操作5;
5.判斷兩個節點是否具有相同父親節點,是則父親節點為LCA,否則繼續操作6;
6.兩個節點同時跳相同長度(但是兩個節點不能跳到同一個節點去)回至操作5.
假設我們求5和6的LCA,那么我們需要先將5從depth:3開始起跳,一步一步向上跳,直到從5跳到2(即與6相同深度)因為2和6有同一個父親節點所以1就是5和6的LCA。
看完這幾張圖你可能認為這個認為這個算法太簡單了(比如說當初不管數據范圍的我)只要瘋狂向上一格一格跳就好了,dfs一遍就能跑出來了。但是一個一個跳這不會T嘛?因此出現了倍增!!!!什么是倍增?表面理解就是按照倍數增大,計算機的基礎是什么?0和1!!!就是二進制!我們可以用二進制來表示所有的數。對於每一個節點我們只要知道其2^j層的祖先是誰,就能讓任意一個節點從自身以logn的速度快速跳躍到1.另賦超生動形象的倍增講解:http://blog.csdn.net/jarjingx/article/details/8180560
核心代碼:
//father[][]第一維是表示節點,第二維表示節點的第i個祖先。 //n是節點個數,Logn是根號n(取整) for(int i=1;i<=Logn;i++) father[u][i]=father[father[u][i-1]][i-1];
練手模版題傳送門:POJ 1330
詳細代碼(模版含解釋):
關於下面這篇代碼處理入度的問題:題目中給出了父親與兒子的關系所以要進行入度處理,不能隨意dfs

#include <cstdio> const int N=1e4+5; using namespace std; int fa[N][14]; int dep[N]; int head[N]; int nx[N]; int to[N]; int tot=1; bool vis[N]; int in[N]; int L,R; void add(int u,int v){//鏈式前向星存圖 to[tot]=v; nx[tot]=head[u]; head[u]=tot++; } void init(int n){//初始化 for(int i=0;i<=n;i++)fa[i][0]=0,in[i]=0,vis[i]=0,head[i]=0,dep[i]=0; tot=1; } void dfs(int u,int d){//dfs處理深度,處理2^j的祖先關系 vis[u]=1; dep[u]=d; for(int i=1;i<14;i++)fa[u][i]=fa[fa[u][i-1]][i-1]; for(int i=head[u];i;i=nx[i]){ int v=to[i]; if(!vis[v])dfs(v,d+1); } } int LCA(int u,int v){ if(dep[u]<dep[v])swap(u,v);//保證u是深度大的那個節點 int d=dep[u]-dep[v];//深度之差 for(int i=0;(1<<i)<=d;i++){//(1<<i)<=d是為了讓u在保證不會跳過v的情況下進行跳躍 if((1<<i)&d){ //(1<<i)&d其實就是轉化二進制問那幾個點可以跳躍 u=fa[u][i];//例如差為5(101)只要在i==0和i==2的情況下跳躍(如果還是不懂就模擬一下) } } if(u==v)return u;//如果兩個節點直接相等那么就如操作4所說,該點就是LCA,否則繼續進行相同長度的跳躍 for(int i=13;i>=0;i--){ if(fa[v][i]!=fa[u][i]){ u=fa[u][i]; v=fa[v][i]; } } return fa[u][0]; } int main(){ int t; scanf("%d",&t); while(t--){ tot=1; int n; scanf("%d",&n); init(n); for(int i=1;i<n;i++){ int l,r; scanf("%d %d",&l,&r); add(l,r); fa[r][0]=l; in[r]++;//處理入度 } scanf("%d%d",&L,&R); for(int i=1;i<=n;i++) if(!in[i]){ dfs(i,0);//必須從入度為0的點開始dfs break; } printf("%d\n",LCA(L,R)); } }

#include<iostream> #include<cmath> #include<algorithm> #include<cstring> #include<cstdio> #include<queue> #include<functional> #include<map> #include<set> #define se second #define fi first #define ll long long #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define Pii pair<int,int> #define pb push_back #define ull unsigned long long #define fio ios::sync_with_stdio(false);cin.tie(0) const double Pi=3.14159265; const double e=2.71828182; const int N=2e5+5; const ull base=163; using namespace std; int head[N]; int to[N]; int nx[N]; int tot=1; int in[N]; int fa[N][20]; int dep[N]; bool vis[N]; void add(int u,int v){ to[tot]=v; nx[tot]=head[u]; head[u]=tot++; } void dfs(int u,int d){ vis[u]=1; dep[u]=d; for(int i=1;i<20;i++){ fa[u][i]=fa[fa[u][i-1]][i-1]; } for(int i=head[u];i;i=nx[i]){ int v=to[i]; if(!vis[v]){ fa[v][0]=u; dfs(v,d+1); } } } int LCA(int u,int v){ if(dep[u]<dep[v])swap(u,v); int d=dep[u]-dep[v]; for(int i=0;(1<<i)<=d;i++){ if((1<<i)&d){ u=fa[u][i]; } } if(u==v)return u; for(int i=19;i>=0;i--){ if(fa[u][i]!=fa[v][i]&&fa[u][i]!=0&&fa[v][i]!=0){ u=fa[u][i]; v=fa[v][i]; } } return fa[u][0]; } int main(){ fio; int n,q; cin>>n>>q; for(int u=2;u<=n;u++){ int v; cin>>v; add(u,v); add(v,u); } dfs(1,0); while(q--){ int a,b,c; cin>>a>>b>>c; int ac=LCA(a,c); int ab=LCA(a,b); int bc=LCA(b,c); int d1=(dep[a]-dep[ac]+dep[c]-dep[ac]+dep[b]-dep[bc]+dep[c]-dep[bc]-(dep[a]-dep[ab]+dep[b]-dep[ab]))/2; int d2=(dep[a]-dep[ab]+dep[b]-dep[ab]+dep[b]-dep[bc]+dep[c]-dep[bc]-(dep[a]-dep[ac]+dep[c]-dep[ac]))/2; int d3=(dep[a]-dep[ab]+dep[b]-dep[ab]+dep[a]-dep[ac]+dep[c]-dep[ac]-(dep[b]-dep[bc]+dep[c]-dep[bc]))/2; cout<<max(d1,max(d2,d3))+1<<endl; } return 0; }
2.Tarjan算法(離線算法)
還是POJ 1330
先附上代碼:

#include <cstdio> const int N=1e4+5; using namespace std; int fa[N]; int dep[N]; int head[N]; int nx[N]; int to[N]; int tot=1; bool vis[N]; int in[N]; int L,R; void add(int u,int v){ to[tot]=v; nx[tot]=head[u]; head[u]=tot++; } int find(int x){ return fa[x]==x?x:fa[x]=find(fa[x]); } void tarjan(int u){ vis[u]=1; for(int i=head[u];i;i=nx[i]){ int v=to[i]; if(!vis[v]){ tarjan(v);//不停的向下走直到再往下就沒有子節點 fa[v]=u;//更新每個節點的父親 } } if(u==L){//如果u是需要查詢兩節點的任意一個節點,就要判斷另一個節點是否已經被更新過,如果被更新過,那么另一個節點的祖先一定是兩個節點的LCA if(fa[R]!=R) printf("%d\n",find(R)); } else if(u==R){ if(fa[L]!=L){ printf("%d\n",find(L)); } } } void init(int n){ for(int i=0;i<=n;i++)fa[i]=i,in[i]=0,vis[i]=0,head[i]=0; tot=1; } int main(){ int t; scanf("%d",&t); while(t--){ tot=1; int n; scanf("%d",&n); init(n); for(int i=1;i<n;i++){ int l,r; scanf("%d %d",&l,&r); add(l,r); in[r]++;//依舊還是從入度為0的點開始 } scanf("%d%d",&L,&R); for(int i=1;i<=n;i++) if(!in[i]){ tarjan(i); break; } } return 0; }
3.RMQ(在線算法)
RMQ(Range Minimum/Maximum Query)區間最大最小查詢,從表面上區間最大最小似乎和LCA一點關系都沒有.但是如果引入歐拉序列,那么我們就可以從歐拉序列的性質解決LCA問題。
什么是歐拉序列?歐拉序列就是訪問到節點的時候將它入隊,回溯回來的時候再入隊一次,每個節點僅入隊2次。但是對於LCA轉RMQ上要略微做些修改。我們將每一次訪問到節點都入隊,將從子樹回來的節點也統統入隊。對於每次LCA相當於找兩個節點在序列中第一出現的位置之間的最小深度的節點。為什么會有這個性質呢?其實很好理解,兩個節點第一次出現的位置之間一定有一部分節點是來自兩個節點之間路徑上的節點。於是我們只要求得任意兩點之間深度最小的節點就可以得到LCA,而區間最小恰恰是RMQ解決的問題,因此可以用RMQ維護區間最小值,O(1)的查詢LCA
依舊是POJ1330

#include<bits/stdc++.h> //CLOCKS_PER_SEC #define se second #define fi first #define ll long long #define Pii pair<int,int> #define Pli pair<ll,int> #define ull unsigned long long #define pb push_back #define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0) const int N=1e5+10; const int INF=0x3f3f3f3f; const int mod=1e9+7; const double eps=1e-7; using namespace std; int head[N],to[N],nx[N],tot=1; bool deg[N]; int dep[N],n,cnt=1,que[N<<1],in[N],out[N]; void add(int u,int v){ nx[tot]=head[u]; to[tot]=v; head[u]=tot++; } struct RMQ { int st[N<<1][32]; inline void init_ST(){ for(int i=1;i<cnt;++i)st[i][0]=i; int k=31-__builtin_clz(cnt-1); for(int j=1;j<=k;++j){ for(int i=1;i+(1<<j)-1<cnt;++i){ int l=st[i][j-1],r=st[i+(1<<(j-1))][j-1]; if(dep[l]<=dep[r]){ st[i][j]=l; } else st[i][j]=r; } } } inline int rmq(int l,int r){ int k=31-__builtin_clz(r-l+1); int L=st[l][k],R=st[r-(1<<k)+1][k]; if(dep[L]<=dep[R])return L; else return R; } inline int lca(int l,int r){ int L=in[l],R=in[r]; if(L>R)swap(L,R); return que[rmq(L,R)]; } }rmq; void dfs(int u,int d){ que[cnt]=u; in[u]=cnt; dep[cnt++]=d; for(int i=head[u];i;i=nx[i]){ int v=to[i]; dfs(v,d+1); que[cnt]=u; dep[cnt++]=d; } } int main(){ int T; scanf("%d",&T); while(T--){ cnt=1; tot=1;memset(head,0,sizeof(head));memset(deg,0,sizeof(deg)); scanf("%d",&n); for(int i=1;i<n;i++){ int u,v;scanf("%d%d",&u,&v); add(u,v); deg[v]=1; } for(int i=1;i<=n;i++){ if(!deg[i]){ dfs(i,1); break; } } rmq.init_ST(); int l,r;scanf("%d%d",&l,&r); printf("%d\n",rmq.lca(l,r)); } return 0; } /* */