最近公共祖先LCAc++
如圖算法
LCA(4,5)=8數組
LCA(10,16)=10spa
LCA(7,3)=4code
求LCA主要算法有:RMQ,tarjan,倍增blog
RMQit
這種方法就是打表io
O(n logn)預處理,O(1)回答class
RMQ就是區間最值查詢。搜索
首先經過dfs求出每一個點的深度
顯然,兩個節點的LCA不只是兩個節點的最近公共祖先,並且包括這兩個節點的最小子樹的根,即包括這兩個節點的最小子樹中的深度最小的節點。
如今,咱們改一下dfs,變成歐拉序。
歐拉序,就是每次從x的父親進入節點x或者從子節點回溯到x都要把x這個編號扔到一個數組的最後。
如圖
歐拉序爲:8 5 9 5 8 4 6 15 6 7 6 4 10 11 10 16 3 16 12 16 10 2 10 4 8 1 14 1 13 1 8
再注意到,一對點的 LCA 不只是包括這兩個節點的最小子樹中的深度最小的節點,仍是鏈接這對點的簡單路徑上深度最小的點。
並且從離開x到進入y的這段歐拉序必然包括全部這對點之間的簡單路徑上的全部點,因此咱們考慮求得這段歐拉序中所包含的節點中的深度最小的點,即他們的LCA。
從x到y的這段歐拉序會包含這棵子樹中的其餘節點,可是不會影響這個最淺點的求得。
顯然,x到y這段歐拉序是個連續區間。
如今咱們考慮經過預處理來O(1)得到這個最淺點。
這裏有一個叫作ST表的東西。
代碼
#include<bits/stdc++.h> using namespace std; struct edge{ int to,next; }ed[100005]; int n,q,u,v,cnt,head[50005],ind,dfn[50005],dep[50005],lg[50005],f[50005][21]; void add(int u,int v){ cnt++; ed[cnt].to=v; ed[cnt].next=head[u]; head[u]=cnt; } void dfs(int u,int fa){ dfn[u]=++ind; dep[u]=dep[fa]+1; f[ind][0]=u; for(int i=head[u];i;i=ed[i].next){ int v=ed[i].to; if(v!=fa)dfs(v,u),f[++ind][0]=u; } } void st(){ for(int j=1;j<=20;j++){ for(int i=1;i+(1<<j)<=ind+1;i++){ int k=i+(1<<(j-1)); if(dep[f[i][j-1]]<dep[f[k][j-1]])f[i][j]=f[i][j-1]; else f[i][j]=f[k][j-1]; } } } int rmq(int l,int r){ if(l>r)swap(l,r); int k=lg[r-l+1]; if(dep[f[l][k]]<dep[f[r-(1<<k)+1][k]])return f[l][k]; else return f[r-(1<<k)+1][k]; } int main(){ scanf("%d%d",&n,&q); lg[0]=-1; for(int i=1;i<=n;i++)lg[i]=lg[i/2]+1; for(int i=1;i<n;i++){ scanf("%d%d",&u,&v); add(u,v),add(v,u); } dfs(1,0); st(); for(int i=1;i<=q;i++){ scanf("%d%d",&u,&v); printf("%d\n",rmq(dfn[u],dfn[v])); } }
Tarjan
一種離線算法,要用到並查集。
時間複雜度爲O(n+q)。
Tarjan算法基於dfs,在dfs的過程當中,對於每一個節點位置的詢問作出相應的回答。
dfs的過程當中,當一棵子樹被搜索完成以後,就把他和他的父親合併成同一集合;在搜索當前子樹節點的詢問時,若是該詢問的另外一個節點已經被訪問過,那麼該編號的詢問是被標記了的,因而直接輸出當前狀態下,另外一個節點所在的並查集的祖先;若是另外一個節點尚未被訪問過,那麼就作下標記,繼續dfs。
如圖
好比:8−1−14−13,此時已經完成了對子樹1的子樹14的dfs與合併,若是存在詢問(13,14),則其LCA即find(14),即1;若是還存在由節點13與已經完成搜索的子樹中的節點的詢問,那麼處理完。而後合併子樹13的集合與其父親1當前的集合,回溯到子樹1,並深搜完全部1的其餘未被搜索過的兒子,並完成子樹1中全部節點的合併,再往上回溯,對節點1進行相似的操做便可。
代碼
#include<bits/stdc++.h> using namespace std; struct edge{ int to,next; }ed[50005]; struct qedge{ int to,next,lca; }qed[50005]; int n,q,u,v,cnt,qcnt,head[50005],qhead[50005],fa[50005]; void add(int u,int v){ cnt++; ed[cnt].to=v; ed[cnt].next=head[u]; head[u]=cnt; } void qadd(int u,int v){ qcnt++; qed[qcnt].to=v; qed[qcnt].next=qhead[u]; qhead[u]=qcnt; } int find(int x){ return fa[x]==x?x:find(fa[x]); } void dfs(int u,int pa){ fa[u]=u; for(int i=head[u];i;i=ed[i].next){ int v=ed[i].to; if(v!=pa){ dfs(v,u); fa[v]=u; } } for(int i=qhead[u];i;i=qed[i].next){ int v=qed[i].to; if(v!=pa&&!qed[i].lca){ qed[i].lca=find(v); if(i%2)qed[i+1].lca=qed[i].lca; else qed[i-1].lca=qed[i].lca; } } } int main(){ scanf("%d%d",&n,&q); for(int i=1;i<n;i++){ scanf("%d%d",&u,&v); add(u,v),add(v,u); } for(int i=1;i<=q;i++){ scanf("%d%d",&u,&v); qadd(u,v),qadd(v,u); } dfs(1,0); for(int i=1;i<=q;i++)printf("%d\n",qed[2*i].lca); }
倍增
時間複雜度O((n+q)logn)
對於這個算法,咱們從最暴力的算法開始:
①若是x和y深度不一樣,先把深度調淺,使他變得和深度小的那個同樣
②如今已經保證了x和y的深度同樣,因此咱們只要把兩個一塊兒一步一步往上移動,直到他們到達同一個節點,也就是他們的最近公共祖先了。
代碼
#include<bits/stdc++.h> using namespace std; struct edge{ int to,next; }ed[100005]; int n,q,u,v,cnt,head[50005],dep[10005],fa[10005]; void add(int u,int v){ cnt++; ed[cnt].to=v; ed[cnt].next=head[u]; head[u]=cnt; } void dfs(int u,int pa){ dep[u]=dep[pa]+1,fa[u]=pa; for(int i=head[u];i;i=ed[i].next){ int v=ed[i].to; if(v!=pa)dfs(v,u); } } int lca(int u,int v){ if(u==v)return u; if(dep[u]==dep[v])return lca(fa[u],fa[v]); if(dep[u]>dep[v])return lca(fa[u],v); if(dep[u]<dep[v])return lca(u,fa[v]); } int main(){ scanf("%d%d",&n,&q); for(int i=1;i<n;i++){ scanf("%d%d",&u,&v); add(u,v),add(v,u); } dfs(1,0); for(int i=1;i<=q;i++){ scanf("%d%d",&u,&v); printf("%d\n",lca(u,v)); } }
但這樣一步一步往上移動太慢,咱們能夠作一個預處理:
設fi,j表示從結點i開始向上走2j步到達的點,因此fi,0=fa(i),fi,1=ff[0][0],0,$fi,j=ff[i][j-1],i-j$
如圖
f5,0=5
f7,1=4
f3,2=ff[3][2-1],2-1=f10,1=8
因而咱們能夠得出如下作法:
1.把x和y移到同一深度(設depx爲節點x的深度),假設depx<depy,從大到小枚舉k,若是depf[y][k]≠depx,那麼y就往上跳。
2.若是x=y,那麼顯然LCA就是fx,0。不然執行第3步。
3.在xx≠yy的狀況下找到深度最小的xx和yy。
代碼
#include<bits/stdc++.h> using namespace std; struct edge{ int to,next; }ed[100005]; int n,q,u,v,cnt,head[50005],dep[50005],f[50005][20]; void add(int u,int v){ cnt++; ed[cnt].to=v; ed[cnt].next=head[u]; head[u]=cnt; } void dfs(int u,int fa){ f[u][0]=fa; dep[u]=dep[fa]+1; for(int i=1;i<20;i++)f[u][i]=f[f[u][i-1]][i-1]; for(int i=head[u];i;i=ed[i].next){ int v=ed[i].to; if(v!=fa)dfs(v,u); } } int lca(int u,int v){ if(dep[u]<dep[v])swap(u,v); for(int i=19;i>=0;i--)if(dep[u]-(1<<i)>=dep[v])u=f[u][i]; if(u==v)return u; for(int i=19;i>=0;i--)if(f[u][i]!=f[v][i])u=f[u][i],v=f[v][i]; return f[u][0]; } int main(){ scanf("%d%d",&n,&q); for(int i=1;i<n;i++){ scanf("%d%d",&u,&v); add(u,v),add(v,u); } dfs(1,0); for(int i=1;i<=q;i++){ scanf("%d%d",&u,&v); printf("%d\n",lca(u,v)); } }