题目:P3379 最近公共祖先(LCA)
分析#
LCA(Lowest Common Ancestor)即树上两个节点 路径上离根最近的公共节点.
常用方法有:
- 倍增法(预处理 ,单次查询 )
- 树链剖分(重链剖分)
- Tarjan 离线算法
最常用、易实现的是倍增法.
倍增法需要:
- 预处理每个节点的 级祖先(即 表示 的第 级祖先).
- 查询时先将 跳到同一深度,然后一起向上跳,距离从 到 ,如果跳到相同的节点就调回来,下一次少跳一些,直到找到最近公共祖先.
具体实现步骤:
- 建树与初始化
- 用邻接表存树.
- 记录每个节点的深度 .
- 记录每个节点的父节点 .
- 预处理倍增表
- 对每个节点 ,.
- 预处理 从 到 .
- 查询 LCA
- 若 深度不同,先将较深的节点向上跳到同一深度.
- 然后从大到小枚举 ,若 ,则 同时跳到各自的 级祖先.
- 最后 和 的父节点就是 LCA.
复杂度分析#
- 预处理:
- 单次查询:
标程#
#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;
}cpp