(YYL: LCA 有三種求法, 你們都知道么?)
(眾神犇: 這哪里來的傻叉...)
1. 樹上倍增
對於求 LCA, 最朴素的方法是"讓兩個點一起往上爬, 直到相遇", "如果一開始不在同一深度, 先爬到同一深度". 樹上倍增求 LCA 的方法同樣基於這個道理, 只不過利用了倍增思想從而加速了"向上爬"的操作. 也就是說, 每次向上爬的高度不是 1, 而是 2 的冪.
我們用 $f(i, j)$ 表示從節點 $i$ 向上爬 $2^j$ 的高度所到達的節點, 則 $f(i, 0)$ 就代表節點 $i$ 的父節點. 那么對於任意的 $f(i, j), j > 0$, 有
$f(i, j) = f(f(i, j-1), j-1)$.
當我們要求兩點的 LCA 時, 先讓它們到同一高度. 這個過程我們使用二進制拆分來加速. 比如當兩點高度相差 $5$ 時, $(5)_{10} = (101)_2$, 那么我們就讓高度較小的那個節點先往上爬 $2^2 = 4$ 步, 再往上 $2^0 = 1$ 步. 此時兩點即在同一高度.
如果爬到同一高度后兩點相同, 顯然這個點就是它們的 LCA, 直接返回即可.
如果兩點不同, 就一起往上爬. 這是一個無限逼近的過程, 直到找到它們的 LCA 的子節點為止. 詳見代碼.
1 for (int i = 1; i <= n; ++i) 2 lg[i] = lg[i - 1] + (1 << lg[i - 1] + 1 == i); 3 4 int lca(int x, int y) { 5 if (dep[x] < dep[y]) 6 swap(x, y); 7 while (dep[x] > dep[y]) 8 x = f[x][lg[dep[x] - dep[y]]]; 9 if (x == y) 10 return x; 11 for (int k = lg[dep[x]]; k >= 0; --k) 12 if (f[x][k] != f[y][k]) 13 x = f[x][k], y = f[y][k]; 14 return f[x][0]; 15 }
(上面的代碼預先算出了 $log_2 (n)$ 的值, 從而簡化了代碼.)
2. Tarjan 算法
Tarjan 算法建立在 DFS 的基礎上.
假如我們正在遍歷節點 x, 那么根據所有節點各自與 x 的 LCA 是誰, 我們可以將節點進行分類: x 與 x 的兄弟節點的 LCA 是 x 的父親, x 與 x 的父親的兄弟節點的 LCA 是 x 的父親的父親, x 與 x 的父親的父親的兄弟節點的 LCA 是 x 的父親的父親的父親... 將這些類別各自歸入不同的集合中, 如果我們能夠維護好這些集合, 就能夠很輕松地處理有關 x 節點的 LCA 的詢問. 顯然我們可以使用並查集來維護.
Tarjan 算法的大致步驟如下:
1. 遍歷 x 節點的子節點. 對於 x 節點的每個子節點, 該子節點遍歷結束之后, 將其整棵子樹合並到 x, 並保證合並之后祖先為 x;
2. 將 x 標記為已遍歷;
3. 處理有關 x 的詢問. 對於詢問 (x, y), 如果 y 節點已遍歷, 則 x 與 y 的 LCA 就是 y 節點所在集合的祖先; 否則, 將其推遲到遍歷 y 時再處理.
代碼如下:
1 void tarjan(int u) { 2 fa[u] = u; 3 4 int i, v; 5 for (i = 0; i < tree[u].size(); i++) { 6 v = tree[u][i]; 7 tarjan(v); 8 fa[findset(v)] = u; 9 } 10 11 vis[u] = true; 12 13 for (i = 0; i < query[u].size(); i++) { 14 if (vis[query[u][i]]) { 15 cnt[findset(query[u][i])]++; 16 } 17 } 18 }
(對於保證合並之后集合祖先為 x 這一步驟, 網絡上的代碼大多使用了一個 ancestor 數組來記錄集合的祖先是誰. 原因是如果使用並查集的帶秩合並, 合並兩個集合之后不好確定根節點到底是誰. 但是帶秩合並在有路徑壓縮的情況下作用有限, 所以這里取消了帶秩合並而直接使用 fa[findset(v)] = u 來保證集合的祖先為 u.)
3. LCA 轉 RMQ
樹上的一些問題可以轉化為對樹的 DFS 序列的操作. 比如對於這樣一棵樹:
(圖片來自 http://scturtle.is-programmer.com/posts/30055.html)
對於以 3 這個節點為根的整棵子樹, 其 DFS 序列為: 3 7 3 8 9 11 9 8 10 12 10 8 3.
假如我們要詢問 7 和 12 的 LCA, 我們找到 7 和 12 分別第一次出現的位置, 然后在這一個區間內找到深度最小的那個節點, 也就是節點 3, 顯然它就是 7 和 12 的 LCA.
記 DFS 序列為 $S[1...2n]$, 節點 $x$ 在序列 $S$ 中第一次出現的位置為 $E[x]$, 用 $RMQ(L, R)$ 表示序列 $S$ 中深度最小的那個節點. 則
$LCA(u, v) = RMQ(E[u], E[v])$
代碼略. DFS + RMQ 的普通做法即可(ST, 線段樹等等).