LCA指的是最近公共祖先(Least Common Ancestors),如下圖所示:
4和5的LCA就是2
那怎么求呢?最粗暴的方法就是先dfs一次,處理出每個點的深度
然后把深度更深的那一個點(4)一個點地一個點地往上跳,直到到某個點(3)和另外那個點(5)的深度一樣
然后兩個點一起一個點地一個點地往上跳,直到到某個點(就是最近公共祖先)兩個點“變”成了一個點
不過有沒有發現一個點地一個點地跳很浪費時間?
如果一下子跳到目標點內存又可能不支持,相對來說倍增的性價比算是很高的
倍增的話就是一次跳2i 個點,不難發現深度差為x時,深度更深的那個點就需要跳x個點
於是可以寫出這段代碼
1 if(depth[a] < depth[b]) swap(a, b); 2 int c = depth[a] - depth[b]; 3 for(int i = 0; i <= 14; i++){ 4 if(c & (1 << i)){ 5 a = up[a][i]; 6 } 7 }
接下來很快就會發現一個很嚴重的問題:兩個點按照這樣跳,不能保證一定是最近的
所以倍增找lca的方法是這樣的:
從最大可以跳的步數開始跳(一定是2i),如果跳的到的位置一樣,就不跳,如果不一樣才跳,每次跳的路程是前一次的一半
過程大概就像上圖所示,但是執行完了這一段到的點不是最近公共祖先,但是,它們再往上跳一格,就到了
把這一段寫成代碼,就成了這樣:
1 for(int i = 14; i >= 0; i--){ 2 if(up[a][i] != up[b][i]){ 3 a = up[a][i]; 4 b = up[b][i]; 5 } 6 }
前面還需要加上一句特判(當a和b在同一邊時,深度淺的那個點就是最近公共祖先)
if(a == b) return a;
好了,會求lca了,關鍵是怎么構造倍增數組。
沒有疑問的是向上跳一格就是自己的父節點
f[i][0] = fa[i];
這個是初值,接着可以根據這個推出來其他的,除此之外還要附上初值0,不然有可能會RE
f[i][j] = f[f[i][j - 1]][j - 1];
就是把這一段路,分成兩段已經知道的
完整代碼就是這樣的:
1 Matrix<int> up; 2 inline void init_bz(){ 3 up = Matrix<int>(16, n + 1); 4 memset(up.p, 0, sizeof(int) * 16 * (n + 1)); 5 for(int i = 1; i <= n; i++){ 6 up[i][0] = fa[i]; 7 } 8 for(int j = 1; j <= 14; j++){ 9 for(int i = 1; i <= n; i++){ 10 up[i][j] = up[up[i][j - 1]][j - 1]; 11 } 12 } 13 }
注意倍增求LCA適用於詢問多的情況,不然光在預處理上花的時間就已經夠多了(如果只有一兩個詢問,直接暴力就好了)
當然,這個倍增算法判斷條件是若干級祖先是否相等。
同樣,點$u$,$v$的LCA還滿足它是其中一個點的最近的一個祖先,滿足$u$,$v$都在它的子樹中。
判斷一個點是否在另一個點的子樹中,我們可以用dfs序來判斷。
這是倍增的另一種判斷方法:
1 void dfs(int p, int fa) { 2 bz[p][0] = fa, in[p] = ++cnt; 3 for (int i = 1; i < bzmax; i++) 4 bz[p][i] = bz[bz[p][i - 1]][i - 1]; 5 for (int i = g.h[p]; ~i; i = g[i].nx) { 6 int e = g[i].ed; 7 if (e == fa) continue; 8 dfs(e, p); 9 } 10 out[p] = cnt; 11 } 12 13 int lca(int a, int b) { 14 if (dep[a] > dep[b]) swap(a, b); 15 if (in[a] <= in[b] && out[a] >= out[b]) 16 return a; 17 for (int i = bzmax - 1, nx; ~i; i--) { 18 nx = bz[a][i]; 19 if (!(in[nx] <= in[b] && out[nx] >= out[b])) 20 a = nx; 21 } 22 return bz[a][0]; 23 } 24