P3379 【模板】最近公共祖先(LCA)

GESP8 冲刺课堂页:先建模,再压缩状态,最后读代码。

一、先回答问题

结论

倍增 LCA 分两步:先深度对齐,再同步上跳到 LCA 下方。

容易误入的路

逐层向上跳在链形树上会退化到 O(n),M=5e5 会超时。

二、核心观察

建模与压缩

up[u][j] 表示 u 向上 2^j 步的祖先,dep[u] 表示深度。

为什么不丢答案:任意上跳步数都能拆成若干个 2 的幂,倍增表不遗漏任何祖先位置。

dep up[u][j] LCA

三、互动演示

步骤1
关键状态 A-
关键状态 B-
答案/目标-
复杂度看表
课堂动作手推

四、自己选答案

问题 1

同步上跳什么时候跳?

问题 2

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;
}