树上要问两个点 u, v 的关系时用 LCA:
- 最近公共祖先:
lca(u, v) - 两点距离:
dep[u]+dep[v]-2*dep[lca] - 树上路径统计 / 树上差分的前置模板
会写最近公共祖先(倍增法)。考场上只要记住四件事就够了:
树上要问两个点 u, v 的关系时用 LCA:
lca(u, v)dep[u]+dep[v]-2*dep[lca]一层一层往上找公共祖先,在链形树上会退化到 O(n)。
查询数 m 很大时(5e5 级)必然超时,必须用倍增把每次查询压到 O(log n)。
dep[u] 是 u 的深度(根深度记为 1);fa[u][j] 是 u 向上跳 2^j 步到达的祖先。
为什么不丢答案:任何上跳步数都能拆成若干个 2 的幂(二进制),所以倍增表能拼出任意祖先位置,一个都不漏。
点「下一步」看两段式查询:橙点 a 先深度对齐,再和蓝点 b 一起同步上跳,最后停在 LCA 下方,父亲就是答案。
同步上跳时,什么条件才往上跳?
深度对齐之后发现 a == b,怎么办?
n = 5×10⁵ 时,LOG 取多少?
建树读入一条边 x y 时要做什么?
| 易错点 | 正确做法 |
|---|---|
| 忘记根节点深度 | dep[root] = 1 |
LOG 取太小 | N=5e5 时 LOG=20 |
没处理 a == b | 深度对齐后先 if (a == b) return a; |
| 同步上跳条件写反 | 是 fa[a][j] != fa[b][j] 才跳 |
| 用 DFS 爆栈 | 大数据用 BFS 建表 |
| 忘记无向边加两次 | g[x].push_back(y); g[y].push_back(x); |
#include <bits/stdc++.h>
using namespace std;
const int N = 500005;
const int LOG = 20; // 2^19 > 5e5,够用
int n, m, s;
int dep[N], fa[N][LOG]; // dep[u]:深度;fa[u][j]:u 向上跳 2^j 步的祖先
vector<int> g[N];
void bfs(int root) { // 大数据用 BFS 建表,避免递归爆栈
queue<int> q;
dep[root] = 1; fa[root][0] = 0; q.push(root);
while (!q.empty()) {
int u = q.front(); q.pop();
for (int v : g[u]) {
if (dep[v]) continue; // 已访问过
dep[v] = dep[u] + 1;
fa[v][0] = u; // 直接父亲
for (int j = 1; j < LOG; j++)
fa[v][j] = fa[fa[v][j - 1]][j - 1];
q.push(v);
}
}
}
int lca(int a, int b) {
if (dep[a] < dep[b]) swap(a, b); // 先保证 a 更深
for (int j = LOG - 1; j >= 0; j--) // 第一步:深度对齐
if (dep[fa[a][j]] >= dep[b]) a = fa[a][j];
if (a == b) return a; // 浅点本身就是 LCA
for (int j = LOG - 1; j >= 0; j--) // 第二步:同步上跳
if (fa[a][j] != fa[b][j]) { a = fa[a][j]; b = fa[b][j]; }
return fa[a][0]; // 此时父亲即 LCA
}
int main() {
cin >> n >> m >> s;
for (int i = 1; i < n; i++) {
int x, y; cin >> x >> y;
g[x].push_back(y); g[y].push_back(x); // 无向边加两次
}
bfs(s);
while (m--) {
int a, b; cin >> a >> b;
cout << lca(a, b) << '\n';
}
return 0;
}
建表三行:fa[v][0]=u 定父亲,循环 fa[v][j]=fa[fa[v][j-1]][j-1] 把 2 的幂层层拼起来。
查询永远是两段:先对齐深度,特判 a==b,再从大步到小步同步跳,最后 return fa[a][0]。
1. fa[u][j] 表示 u 向上跳 2^j 步的祖先。
2. 查询时先把深的点跳到和另一个点同一深度。
3. 再从大到小一起跳,跳到 LCA 下面一层,父亲就是答案。