題目描述
如題,給定一棵有根多叉樹,請求出指定兩個點直接最近的公共祖先。
輸入輸出格式
輸入格式:
第一行包含三個正整數N、M、S,分別表示樹的結點個數、詢問的個數和樹根結點的序號。
接下來N-1行每行包含兩個正整數x、y,表示x結點和y結點之間有一條直接連接的邊(數據保證可以構成樹)。
接下來M行每行包含兩個正整數a、b,表示詢問a結點和b結點的最近公共祖先。
輸出格式:
輸出包含M行,每行包含一個正整數,依次為每一個詢問的結果。
輸入輸出樣例
5 5 4 3 1 2 4 5 1 1 4 2 4 3 2 3 5 1 2 4 5
4 4 1 4 4
說明
時空限制: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。
Solution:
題目來源:Luogu P3379
我們來講講LCA的倍增求法,來自於LS學長神犇的教授,加上我自身的理解,可能對各位新人的幫助會更有理解作用,所以我決定分享一下。(蒟蒻一本正經)
LCA——最近公共祖先。最朴素的算法無疑是從兩個點一個個往上走,出現的第一個兩個點都走過的點即為兩點的LCA。這個無需多加解釋。
然而這樣時間復雜度非常高。尤其是當樹退化成一條鏈的時候,每次查詢的復雜度將會飆到O(N)。
為此,倍增的作用就是將兩點上升所需的復雜度減低。而原理就是,把兩點往上跳,跳到LCA.-.
流程概括為:將深度不同的兩點跳到同一層(深一點的跳到淺一點的一層),然后再將兩點向上跳到LCA的下面一層,最后再向上跳一層。(后面一部分下面會解釋,為什么不能直接跳到LCA。)
快速跳的方法,每次向上跳的層數都為2^i層。(i為非負整數),相當於把層數差轉成2進制數,一位一位往上跳,使層數差快速減小。這樣,每次查詢復雜度最高就只會為log2(N)。
實現方法:
我們首先需要兩個數組。f[i][j]代表從i點向上跳2^j層后所到達的點,dep[i]代表這棵樹中點i的深度。
dep數組可以在從根深度遍歷整棵樹的時候求得。
f數組利用了遞推的思想。遞推式為:f[i][j]=f[f[i][j-1]][j-1]。初始化f[i][0]也可以在遍歷整棵樹的時候求得。
之后,我們處理LCA時,按照上面的流程。我們先將較深的點一次次往上逼近較淺的點,直至他們層數相同。(相當於把層數差的二進制一位一位減去,直至變成0)
然后,兩點再同時向上逼近。i從最高位開始枚舉,假設兩點分別為x,y,那么能向上跳的判斷式為:if f[x][i]!=f[y][i] then x=f[x][i],y=f[y][i]。翻譯成人話就是如果兩點向上跳了2^i層以后不到同一個點就接着往上跳。為什么這樣?因為如果往上跳了2^i層,即使到了同一個點,它不一定是兩點的LCA。(為什么?)
這樣做,最終就會到達LCA的下面一層。隨后,我們再將兩點向上跳一層。LCA求得。
為什么最終會到達LCA的下面一層?
我們假設從a,b點開始,往上跳2^j層,跳到同一點。不跳。往上跳2^(j-1)層,不跳到同一點,往上跳,分別到了A',B'。顯然,這種情況是一定會存在的。
那么,從A',B’再往上跳到原來那個決定不跳的點,顯然要跳2^(j-1)層。那么,那個點有可能是LCA,也有可能不是,對吧?所以,從A',B'往上跳到LCA所需的層數,是≤2^(j-1)的。
所以,從A',B'跳到LCA的下面一層X的所需層數,會是<2^(j-1)的。換句話來說,A',B'到X的層數變成了一個j-2位的二進制數(可能會有前導零,也就是還可能會跳到點數相同的地方)。而此時,剛好枚舉到j-2位。那么,前導零不減,再這么減下去,你發現,這個層數差最終會變成0,而你最終也會到達第X層。(為什么?)
如果不懂,也可以先放着。你只需要知道這么做可以到LCA的下面一層就OK了orz
請最好自己手畫一棵樹模擬一下整個算法的過程。
蒟蒻解釋難免有錯誤,如果出現錯誤,請回復我,我將感激不盡orz
代碼如下:
#include<bits/stdc++.h> using namespace std; const int N=500010,OP=log2(500000)+1; int dpt[N][26]; int dep[N]; int gi(){ int x=0; char ch=getchar(); while(ch<'0'||ch>'9')ch=getchar(); while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar(); return x; } int h[N],to[N*2],nexp[N*2],p=1; inline void ins(int a,int b){ nexp[p]=h[a],h[a]=p,to[p]=b,p++; } void dfs(int p,int x){ dep[x]=p; for(int u=h[x];u;u=nexp[u]){ if(!dep[to[u]]){ dpt[to[u]][0]=x; dfs(p+1,to[u]); } } } int LCA(int a,int b){ if(dep[a]<dep[b])swap(a,b); for(int j=OP;j>=0;j--) if(dep[a]-(1<<j)>=dep[b])a=dpt[a][j]; if(a!=b){ for(int j=OP;j>=0;j--) if(dpt[a][j]!=dpt[b][j])a=dpt[a][j],b=dpt[b][j]; a=dpt[a][0]; } return a; } int main(){ int n,q,r; cin>>n>>q>>r; int a,b; for(int i=0;i<n-1;i++){ a=gi(),b=gi(); ins(a,b),ins(b,a); } dfs(1,r); for(int j=1;j<=OP;j++) for(int i=1;i<=n;i++) dpt[i][j]=dpt[dpt[i][j-1]][j-1]; for(int i=0;i<q;i++){ a=gi(),b=gi(); printf("%d\n",LCA(a,b)); } return 0; }
為什么與X層數差最終會變成0?
假設你已經會寫這個算法了。
首先我們證明,前導零不會被減去。假設與X層的層數差為x',而你正准備往上跳y層。由於LCA的層數是X+1,而LCA往上的點它都不會跳,對吧?(反而,如果LCA往下的點,也就是層數<=X,也就是y<x'+1,它都會往上跳)所以如果y>=x'+1,那么就絕對不會往上跳。
顯然,當x'的該位為0,且屬於前導零,那么只需證明x'+1<=y。而這個非常易證(假設y為10000,而x'滿足條件的最大值為01111)。所以保證,前導零是不會減去的。
接着我們證明,一旦枚舉到了x'的第一個為1的位數,這個1絕對會被減去。按照同樣的方法,假設y為10000,而x'滿足條件的最小值為10000,所以y<x'+1.
兩點合在一起,前導零不會減去,枚舉到一個1位就減去,最終這個層數差就會變成0.證畢。