视频加载失败

最近公共祖先

817 字
4 分钟
最近公共祖先
题目:P3379 最近公共祖先(LCA)

如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先.

输入格式#

第一行包含三个正整数 N,M,SN,M,S,分别表示树的结点个数、询问的个数和树根结点的序号.

接下来 N1N-1 行每行包含两个正整数 x,yx, y,表示 xx 结点和 yy 结点之间有一条直接连接的边(数据保证可以构成树).

接下来 MM 行每行包含两个正整数 a,ba, b,表示询问 aa 结点和 bb 结点的最近公共祖先.

输出格式#

输出包含 MM 行,每行包含一个正整数,依次为每一个询问的结果.

数据范围#

对于 100%100\% 的数据,1N,M5×1051 \leq N,M\leq 5\times10^51x,y,a,bN1 \leq x, y,a ,b \leq N不保证 aba \neq b

分析#

LCA(Lowest Common Ancestor)即树上两个节点 u,vu,v 路径上离根最近的公共节点.

常用方法有:

  • 倍增法(预处理 O(nlogn)O(n\log n),单次查询 O(logn)O(\log n)
  • 树链剖分(重链剖分)
  • Tarjan 离线算法

最常用、易实现的是倍增法

倍增法需要:

  • 预处理每个节点的 2k2^k 级祖先(即 fa[u][k]fa[u][k] 表示 uu 的第 2k2^k 级祖先).
  • 查询时先将 u,vu,v 跳到同一深度,然后一起向上跳,距离从 2logn2^{\log n}202^0,如果跳到相同的节点就调回来,下一次少跳一些,直到找到最近公共祖先.

具体实现步骤:

  1. 建树与初始化
    • 用邻接表存树.
    • 记录每个节点的深度 deep[u]deep[u]
    • 记录每个节点的父节点 fa[u][0]fa[u][0]
  2. 预处理倍增表
    • 对每个节点 uufa[u][k]=fa[fa[u][k1]][k1]fa[u][k] = fa[fa[u][k-1]][k-1]
    • 预处理 kk11logn\log n
  3. 查询 LCA
    • u,vu,v 深度不同,先将较深的节点向上跳到同一深度.
    • 然后从大到小枚举 kk,若 fa[u][k]fa[v][k]fa[u][k] \neq fa[v][k],则 u,vu,v 同时跳到各自的 2k2^k 级祖先.
    • 最后 uuvv 的父节点就是 LCA.

复杂度分析#

  • 预处理:O(nlogn)O(n\log n)
  • 单次查询:O(logn)O(\log n)

标程#

#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 100;
int n, m, s;
vector<int> edge[N];
int deep[N];
int fa[N][23];
// 预处理
void getInfo(int t) {
// 预处理父节点
deep[t] = deep[fa[t][0]] + 1;
// 预处理祖宗节点
for (int i = 1; i < 23; ++i)
fa[t][i] = fa[fa[t][i - 1]][i - 1];
// 递归处理所有的边
for (int i = 0; i < edge[t].size(); ++i) {
int to = edge[t][i];
if (to == fa[t][0]) continue;
fa[to][0] = t;
getInfo(to);
}
}
// 获得t节点向上跳v格跳到的节点编号
int jump(int t, int v) {
for (int i = 22; i >= 0; --i) {
if (v >= (1 << i)) {
t = fa[t][i];
v -= (1 << i);
}
}
return t;
}
// 获取节点x和节点y的lca
int lca(int x, int y) {
// 统一深度
if (deep[y] > deep[x]) y = jump(y, deep[y] - deep[x]);
else x = jump(x, deep[x] - deep[y]);
// 处理x和y是同一支上的,有血缘关系的情况
if (x == y) return x;
// 向上跳
for (int i = 22; i >= 0; --i) {
if (fa[x][i] == fa[y][i]) continue;
x = fa[x][i], y = fa[y][i];
}
return fa[x][0];
}
int main() {
cin >> n >> m >> s;
for (int i = 1; i <= n - 1; ++i) {
int x, y;
cin >> x >> y;
edge[x].push_back(y);
edge[y].push_back(x);
}
getInfo(s);
for (int i = 1; i <= m; ++i) {
int x, y;
cin >> x >> y;
cout << lca(x, y) << endl;
}
return 0;
}

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

最近公共祖先
https://blog.jerrylab.top/posts/graph/lca/
作者
Jerry
发布于
2026-03-02
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Jerry
Hello, I'm Jerry.
公告
欢迎来到我的博客!这是一则示例公告。
分类
标签
最新动态
站点统计
文章
80
分类
3
标签
35
总字数
89,198
运行时长
0
最后活动
0 天前
站点信息
构建平台
Cloudflare Pages
博客版本
Firefly v6.16.5
文章许可
CC BY-NC-SA 4.0