核心概述

最近公共祖先(Lowest Common Ancestor, LCA)在树上求两点 u、v 的最近公共祖先,典型实现包括二进制提升与欧拉序+RMQ,预处理 O(N log N),查询 O(log N)/O(1)。

原文引述

Description: Find the lowest common ancestor on a rooted tree. Supports binary lifting or Euler tour with RMQ. Time: Build O(N log N); Query O(log N) with lifting or O(1) with RMQ

展开阐述

定义与背景

  • 问题:在根树或森林中,给定两个节点 u、v,返回同时为二者祖先的节点中离它们最近者。
  • 典型适用:路径距离/路径分解、虚树构建、树上 DP 的分支合并、重链剖分/HLD 的配套能力等。
  • 能力边界:LCA 在同一棵树内有意义;森林需分别处理或连虚根。

接口/字段/数据结构(据常见实现)

  • 构造与建树
    • LCA(n):构造 n 节点结构;
    • addEdge(u,v):无向边建树;
    • init(root=0):以 root 为根预处理(可对每棵树分别调用)。
  • 查询接口
    • lca(u,v):返回最近公共祖先;
    • dist(u,v):depth[u] + depth[v] − 2·depth[lca(u,v)];
    • jmp(u,k):从 u 向上跳 k 步(越界返回 -1)。
  • 必要数据
    • depth[v]:深度;
    • up[k][v]:2^k 祖先(倍增法),或
    • euler、first[v]、深度数组 + RMQ 结构(欧拉 + RMQ)。

实现A:二进制提升(倍增)

  • 预处理
    • DFS 记录 depth[v] 与 up[0][v]=parent[v];
    • 对 k=1..LOG:up[k][v] = up[k-1][ up[k-1][v] ]。
  • 查询 lca(u,v)
    1. 若 depth[u] < depth[v] 则交换;
    2. 将 u 向上跳 depth 差;
    3. 自大到小枚举 k:若 up[k][u] ≠ up[k][v],则同时上跳;
    4. 返回 up[0][u]。
  • 复杂度
    • 预处理 O(N log N);查询 O(log N)。

实现B:欧拉序 + RMQ

  • 欧拉遍历
    • 记录 DFS 过程中节点访问序列 e,以及每个节点首次出现位置 first[v];
    • 保存访问时的深度数组。
  • RMQ
    • LCA(u,v) 等价为 e 在 [first[u], first[v]] 范围内深度最小的节点;
    • 用稀疏表/线段树预处理 RMQ:O(M log M),M≈2N−1;查询 O(1)/O(log M)。
  • 适用性

输出语义

  • lca(u,v):返回最近公共祖先;
  • dist(u,v):返回边数距离(加权树可在 DFS 中维护前缀权得加权距离);
  • jmp(u,k):若不存在则返回 -1。

复杂度与边界条件

  • 复杂度
    • 倍增:预处理 O(N log N),查询 O(log N);
    • 欧拉+RMQ:预处理 O(N log N),查询 O(1)(稀疏表)。
  • 边界与一致性
    • 根选择仅影响 depth,不影响 LCA 正确性;
    • 森林:对每棵树分别 init,或加虚根;
    • up[k][root] 可置 -1 或 root,自洽即可;查询时注意越界保护。

变体/扩展与关系

  • 066-图论-重链剖分:若需要路径聚合/修改,HLD 更合适,LCA 只是副产品;
  • 069-图论-LinkCutTree:LCT 用于动态树,在线 link/cut 场景;静态树优先 LCA/倍增;
  • 054-图论-二进制提升:倍增条目更细化了上跳与 LCA 的模板实现;
  • 与欧拉+RMQ:查询极多且静态时选 RMQ,需路径分解或多类查询选 HLD。

正确性要点与不变式

  • 祖先闭包不变式:up[k][v] 始终为 v 的祖先(或哨兵);
  • 深度对齐:上跳将较深节点抬至与另一点同深度,不会跨越 LCA;
  • 分离原则:自高到低同步上跳时保持“尚未越过 LCA”的不变式,最终父即 LCA。

与相邻技术的对比与取舍建议

关联节点