前言
“倍增”,作為一種二進制拆分思想,廣泛用於各中算法,如$ST$表,求解$LCA$等等...今天,我們僅討論用該思想來求解樹上兩個節點的$LCA$(最近公共祖先)
“倍增”是什么東西?
倍增就是“成倍增加”的意思,比如$1$倍增后變成了$2$,$2$倍增后就變成了$4$,$4$變成$8$,以此類推...
實現
一直向上LCA
在講真正的倍增之前,我們先來說說最朴素的$LCA$,對於需要求解的兩個點$(x,y)$,我們最先能想到的方法就是兩個點先到達同一深度,然后一直往上跳父親,知道兩個點跳到同一個點上,這個點就是$LCA$。
int LCA (int x, int y) {
if (depth[x] < depth[y]) swap(x, y);
while(depth[x] != depth[y]) x = fa[x];
while(x != y) x = fa[x], y = fa[y];
return x;
}
不難發現,這種算法的時間開銷很大,我們想辦法來優化它。
倍增LCA
就如同$ST$表一樣,我們不妨設$f[i][j]$表示樹上編號為$i$的節點向上跳$2^j$個節點后所達到的節點,如同$ST$表的預處理,我們很容易發現如何預處理出這個$f$數組:
f[i][j] = f[f[i][j-1]][j-1];
顯然,$i$往上跳$2{j-1}$次之后再跳$2{j-1}$次之后就相當於$i$往上跳$2^j$次,我們可以借此來優化,利用二進制優化背包的思想那樣,將跳的次數二進制拆分。
於是,我們改寫一下之前的代碼
int LCA (int x, int y) {
if (depth[x] < depth[y]) swap(x, y);
for (int i = LogN; i >= 0; --i)
if (depth[f[x][i]] >= depth[y])
x = f[x][i];
if (x == y) return x;
for (int i = LogN; i >= 0; --i)
if (f[x][i] != f[y][i])
x = f[x][i], y = f[y][i];
return f[x][0];
}
這樣一來,速度就快很多了,由原來的$O(Depth)$變成了現在的$O(log_2(Depth))$
代碼
#include <cstdio>
#include <cstring>
typedef int ll;
const ll N = 5e5 + 10, M = 5e5 + 10, LogN = 25;
ll n, m, s, depth[N], f[N][LogN], a, b, c;
ll from[N], to[M << 1], nxt[M << 1], cnt, tmp, Log[N];
inline void swap (ll &a, ll &b) {tmp = a, a = b, b = tmp;}
//鏈式前向星加邊
void addEdge (ll u, ll v) {
to[++cnt] = v, nxt[cnt] = from[u], from[u] = cnt;
}
//計算深度&計算祖先
void doit (ll u, ll fa) {
depth[u] = depth[fa] + 1;
for (register ll i = 1; i <= Log[n]; ++i) {
if ((1 << i) >= depth[u]) break;
f[u][i] = f[f[u][i - 1]][i - 1];
}
for (register ll i = from[u]; i; i = nxt[i]) {
ll v = to[i];
if (v == fa) continue;
f[v][0] = u;
doit (v, u);
}
}
//計算LCA
inline ll LCA (ll x, ll y) {
if (depth[x] < depth[y]) swap(x, y);
//我們默認x為更深的那個點
for (register ll i = 0; i <= Log[n]; ++i)
if (depth[f[x][i]] >= depth[y])
x = f[x][i];
//將x跳到和y同一深度上
if (x == y) return x;
for (register ll i = Log[n]; i >= 0; --i)
if (f[x][i] != f[y][i])
x = f[x][i], y = f[y][i];
//一起向上跳
return f[x][0];
//不難看出,此時兩個點均在其LCA的下方,往上跳一次即可
}
int main () {
scanf ("%d%d", &n, &m);//n節點數 m詢問次數
Log[0] = -1;
for (register ll i = 1, u, v; i < n; ++i) {
scanf ("%d%d", &u, &v);
addEdge (u, v); addEdge(v, u);
Log[i] = Log[i >> 1] + 1;
}
Log[n] = Log[n >> 1] + 1;
doit (1, 0);
while (m--) {
scanf ("%d%d", &a, &b);
printf ("%d\n", LCA(a, b)));
}
return 0;
}