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 步到达的祖先。

fa[u][0] 是父亲;
fa[u][j] = fa[ fa[u][j-1] ][j-1]  ←  先跳一半,再跳一半

为什么不丢答案:任何上跳步数都能拆成若干个 2 的幂(二进制),所以倍增表能拼出任意祖先位置,一个都不漏。

dep[u] 深度 fa[u][0]=父亲 fa[u][j] 倍增表 LOG=20

三、互动演示:查 LCA(10, 8)

点「下一步」看两段式查询:橙点 a 先深度对齐,再和蓝点 b 一起同步上跳,最后停在 LCA 下方,父亲就是答案。

a(较深的点) b(较浅的点) LCA 答案
dep1 dep2 dep3 dep4 dep5 1 2 3 4 5 6 7 8 10
阶段准备
步骤0 / 5
a 所在结点10 · dep5
b 所在结点8 · dep4

四、自己选答案

问题 1

同步上跳时,什么条件才往上跳?

问题 2

深度对齐之后发现 a == b,怎么办?

问题 3

n = 5×10⁵ 时,LOG 取多少?

问题 4

建树读入一条边 x y 时要做什么?

五、考前易错点

易错点正确做法
忘记根节点深度dep[root] = 1
LOG 取太小N=5e5LOG=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);

六、C++14 完整模板

#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 下面一层,父亲就是答案。