📘最近公共祖先 LCA

2026-08-08
⭐⭐ GESP 8级

📖概念讲解

最近公共祖先(Lowest Common Ancestor, LCA)是树上两个节点的最近的公共祖先节点——即从根出发,同时是两个节点祖先、且深度最大的那个。

⚡ 核心思想:对于节点 u 和 v,它们的 LCA 就是从 u 到根的路径和从 v 到根的路径最后交汇的那个节点。两个节点一定在其 LCA 的不同子树中(或有一个就是 LCA 本身)。

常用的 LCA 算法有三种:

GESP 8级主要掌握倍增法,理解 f[i][j] 表示"节点 i 的第 2^j 级祖先"这个关键定义。

💻代码示例

倍增法求 LCA(最常考写法):

1#include<bits/stdc++.h>
2using namespace std;
3
4const int N = 500010, LOG = 20;
5vector<int> g[N]; // 邻接表存树
6int dep[N], f[N][LOG]; // dep=深度, f[i][j]=节点i的第2^j级祖先
7
8// DFS预处理:求深度 + 倍增表
9void dfs(int u, int fa) {
10 f[u][0] = fa; // 第 2^0=1 级祖先就是直接父亲
11 for (int j = 1; j < LOG; j++)
12 f[u][j] = f[f[u][j-1]][j-1]; // 倍增核心:2^j = 2^(j-1) + 2^(j-1)
13 for (int v : g[u])
14 if (v != fa) {
15 dep[v] = dep[u] + 1; // 子节点深度 = 父+1
16 dfs(v, u);
17 }
18}
19
20// 把节点 x 向上跳 k 步
21int lift(int x, int k) {
22 for (int j = 0; j < LOG; j++)
23 if (k >> j & 1) // k的二进制第j位为1时跳2^j步
24 x = f[x][j];
25 return x;
26}
27
28// 求 u 和 v 的最近公共祖先
29int lca(int u, int v) {
30 if (dep[u] < dep[v]) swap(u, v); // 保证u更深
31 u = lift(u, dep[u] - dep[v]); // 第一步:把u跳到和v同一深度
32 if (u == v) return u; // 巧合就是LCA
33 for (int j = LOG-1; j >= 0; j--) // 从大到小尝试跳
34 if (f[u][j] != f[v][j]) { // 还没相遇就跳
35 u = f[u][j];
36 v = f[v][j];
37 }
38 return f[u][0]; // 最后跳一步到LCA
39}
40
41int main() {
42 int n = 7, m = 5, root = 1;
43 // 建树:1-2, 1-3, 2-4, 2-5, 3-6, 3-7
44 int edges[][2]={{1,2},{1,3},{2,4},{2,5},{3,6},{3,7}};
45 for (auto& e : edges) { // 无向边,双向加
46 g[e[0]].push_back(e[1]);
47 g[e[1]].push_back(e[0]);
48 }
49 dfs(root, 0); // 从根开始预处理
50
51 printf("LCA(4,5) = %d\n", lca(4,5)); // 输出 2
52 printf("LCA(4,6) = %d\n", lca(4,6)); // 输出 1
53 printf("LCA(6,7) = %d\n", lca(6,7)); // 输出 3
54 return 0;
55}
56# 输出: LCA(4,5)=2, LCA(4,6)=1, LCA(6,7)=3

🧩互动小测

❓ 倍增表 f[i][j] 的含义是?

❓ 在 lca 函数中,为什么要从大到小遍历 j(从 LOG-1 到 0)?

❓ 倍增法求 LCA 的预处理时间复杂度是?

🏋️动手练一练

📝 编程练习

给定一棵 n 个节点的树(1 为根),以及 q 次查询,每次查询两个节点 u 和 v,求它们的最近公共祖先。

输入格式:第一行 n, q;接下来 n-1 行每行两个整数表示边;再 q 行每行两个整数 u, v。
输出格式:q 行,每行一个整数表示 LCA。
提示:用倍增法预处理,然后对每个查询 O(log n) 求解。注意根节点的祖先设为 0,dfs 时判断 v != 0。
参考答案:
// 直接用上面的倍增模板即可
// 注意读入时 n-1 条边建无向图
// 每次查询调用 lca(u, v) 输出结果
int main() {
    int n, q;
    cin >> n >> q;
    for (int i = 1; i < n; i++) {
        int a, b; cin >> a >> b;
        g[a].push_back(b);
        g[b].push_back(a);
    }
    dep[1] = 0;
    dfs(1, 0);  // 预处理
    while (q--) {
        int u, v; cin >> u >> v;
        cout << lca(u, v) << "\n";
    }
    return 0;
}

要点:预处理 O(n log n),每次查询 O(log n),总复杂度 O((n+q) log n),足以处理 5×10^5 规模。

📝易错点提醒

学完这个知识点后点一下