【算法】LCA 最近公共祖先
发布于: 2025-10-18 更新于: 2025-10-19 分类于:  阅读次数: 

问题引入

如果现在给你一棵树,要求求出任意两点之间的的最近公共祖先(LCA),你该怎么做呢?

树上最近公共祖先

  • 树上两点的所有公共祖先中离两点最近的点

朴素算法求LCA

这个想法很简单,就是把从根节点到两个节点的路径分别求出来,然后找出路径中离根节点最远的两边同时存在的点即可,时间复杂度最坏 O(N)N是节点数。

复杂度证明 :

找到路径是 O(N)(全图遍历),匹配的时候最劣情况 O(N),树是一条链并且 u,v 是同一个叶子节点。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
vector<int> g[maxn];
vector<vector<int>> pth[maxn];
void dfs(int u,int fa) {
for (auto v:g[u]) {
if (v==fa) continue;
pth[v]=pth[u]; pth[u].push_back(u);
dfs(v,u);
}
}
int solve(int u,int v) {
int l=min(pth[u].size(),pth[v].size());
for (int i=0;i<l;i++) {
if (pth[u][i]!=pth[v][i]) {
if (i) return i-1;
else return -1; // 无解
}
}
}
void init() {
dfs(root,0); // 这里 root 是根节点
}

当然,还有另一种想法,就是让两个点一直往他的父亲走,先让两个到同一层,然后一起往上跳跃,直到找到第一个不一样的,时间复杂度依旧是 O(N)

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int fa[maxn]; // 每个节点的父亲,根节点父亲为-1
int dep[maxn]; // 每个节点的深度
void dfs(int u,int _fa) {
fa[u]=_fa;
for (auto v:g[u]) {
if (v==_fa) continue;
dep[v]=dep[u]+1;
dfs(v,u);
}
}
int solve(int u,int v) {
if (dep[u]>dep[v]) swap(u,v);
while (dep[u]!=dep[v]) v=fa[v]; // 走到同一深度
while (fa[u]!=fa[v]) u=fa[u],v=fa[v];
return fa[u];
}
void init() {
dep[root]=0;
dfs(root,-1);
}

倍增算法求LCA

每次查询 O(N) 显然不是我们想要的复杂度,于是考虑优化。

思想

根据第二种朴素算法,我们每次只能跳一步,这是这个算法的瓶颈,那我们能不能一步跳多一点呢?

首先,我们知道,任何一个数都可以表示成若干个不同的 2^k 的和(因为每个数都有一个二进制),所以我们每次跳一步可以换成每次向上跳 2^k 步,若干次后一定可以跳到同一层。

所以如果能在 O(1) 的时间内得到向上跳 2^k 步的点,我们就可以在 O(log N) 的时间内跳到同一层。

同理,现在到了同一层要一步一步往上找,因为往上跳找祖先也可以变成每次跳2的次方步。但是,有一个问题,现在我们不知道要跳多少层了,那该怎么办呢?

这时候我们可以从大往小枚举 k 的值(从小到大可能虽然可以跳到这个点,但是不是最优,比如应该跳 12 步,正确跳法是 84,但是从小往大会跳 1,2,4 然后发现 8 跳不了,于是炸了qwq),如果跳 2^k 次方步两边已经相同了,那就不能跳到这个点。

1
2
3
4
5
6
7
8
9
    1
|
2
|
3
/ \
4 5
/
6

如图,求 56 去找公共祖先,先找到同一层 45,然后往上跳,因为从大到小枚举,所以跳 2 步的时候会跳到 2 这个点,虽然可以,但不是最优。

所以我们跳的时候要注意跳到的那个点不能是相等的,这样的话可以跳到最近共同祖先的下一个点,然后答案就是再往上走一步就可以了。

然后,我们还有一个问题,怎么求跳的点?还记得倍增吗,我们可以用它求解。

代码随之而出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const int maxn=1e5+10; // 点的最大数
const int maxlog=30; // 2^30 肯定大于点的个数,没有问题
vector<int> g[maxn]; // 题目给出
int lg[maxn];
int dep[maxn];
int fa[maxn][maxlog];
void dfs(int u,int _fa) {
fa[u][0]=_fa;
dep[u]=dep[_fa]+1;
for (auto v:g[u]) {
if (v==_fa) continue;
dfs(v,u);
}
}
void init(int n) {
dfs(1,0);
for (int j=1;j<maxlog;j++) {
for (int i=1;i<=n;i++) {
fa[i][j]=fa[fa[i][j-1]][j-1];
}
}
}
int LCA(int u,int v) {
if (dep[u]>dep[v]) swap(u,v);
for (int i=maxlog-1;i>=0;i--) { // 跳到同一层
if (dep[u]<=dep[fa[v][i]]) v=fa[v][i];
}
if (u==v) return u; // 特判
for (int i=maxlog-1;i>=0;i--) { // 找最近公共祖先
if (fa[u][i]!=fa[v][i]) u=fa[u][i],v=fa[v][i];
}
return fa[u][0];
}

算一算时间复杂度,初始化是 O(N) (遍历 O(N),倍增 O(N),常数 30)跟之前一样,询问是 O(log N),比之前快了好多~

模板 - 洛谷P3379 【模板】最近公共祖先(LCA) - 题目传送门

例题1 - HDU2586 How far away ?

例题2 - 洛谷P3128 [USACO15DEC] Max Flow P - 题目传送门 题解传送门

--- 本文结束 The End ---