倍增 LCA 分两步:先深度对齐,再同步上跳到 LCA 下方。
P3379 【模板】最近公共祖先(LCA)
GESP8 冲刺课堂页:先建模,再压缩状态,最后读代码。
一、先回答问题
逐层向上跳在链形树上会退化到 O(n),M=5e5 会超时。
二、核心观察
up[u][j] 表示 u 向上 2^j 步的祖先,dep[u] 表示深度。
为什么不丢答案:任意上跳步数都能拆成若干个 2 的幂,倍增表不遗漏任何祖先位置。
dep
up[u][j]
LCA
三、互动演示
步骤1
关键状态 A-
关键状态 B-
答案/目标-
复杂度看表
课堂动作手推
点击下一步开始。
四、自己选答案
同步上跳什么时候跳?
先判断,再点按钮。
a=b 怎么办?
先判断,再点按钮。
五、手推结果
| 阶段 | 目标 |
|---|---|
| 预处理 | 2^j 祖先 |
| 对齐 | 深度相同 |
| 同步 | 停在 LCA 下方 |
课堂要求:每题先说清对象、关系、保留的信息、压缩掉的信息,再写代码。
六、C++14 参考代码
#include <bits/stdc++.h>
using namespace std;
inline int rd() {
int x = 0; char c = getchar();
while (c < '0' || c > '9') c = getchar();
while (c >= '0' && c <= '9') { x = x * 10 + c - 48; c = getchar(); }
return x;
}
const int N = 500005, LOG = 20;
int n, m, s, dep[N], up[N][LOG];
vector<int> g[N];
void bfs(int root) { // 大数据用 BFS 求深度,避免递归爆栈
queue<int> q;
dep[root] = 1; up[root][0] = 0; q.push(root);
while (!q.empty()) {
int u = q.front(); q.pop();
for (int v : g[u]) if (!dep[v]) {
dep[v] = dep[u] + 1; up[v][0] = u;
for (int j = 1; j < LOG; j++) up[v][j] = up[up[v][j - 1]][j - 1];
q.push(v);
}
}
}
int lca(int a, int b) {
if (dep[a] < dep[b]) swap(a, b);
for (int j = LOG - 1; j >= 0; j--) // 1. 深度对齐
if (dep[up[a][j]] >= dep[b]) a = up[a][j];
if (a == b) return a;
for (int j = LOG - 1; j >= 0; j--) // 2. 同步上跳
if (up[a][j] != up[b][j]) { a = up[a][j]; b = up[b][j]; }
return up[a][0];
}
int main() {
n = rd(); m = rd(); s = rd();
for (int i = 1; i < n; i++) {
int x = rd(), y = rd();
g[x].push_back(y); g[y].push_back(x);
}
bfs(s);
while (m--) { int a = rd(), b = rd(); printf("%d\n", lca(a, b)); }
return 0;
}