一、概念
在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先,就是:
两个节点在这棵树上深度最大的公共的祖先节点。
二、暴力求LCA
暴力的方法便是先对其中一个要求LCA的节点向上搜索,标记这个节点走过的每个父亲节点,然后再对另一个点向上搜索,若走过了一个已经被标记的节点,则表示它们同时都能到达这个父亲节点,那么这个父亲节点就是LCA
三、倍增求LCA

倍增就是以1,2,4,8,16…等2的倍数向上跳跃找LCA,比起暴力的做法,因为向上搜索的间隔变大了许多,所以更能节约时间:
时间复杂度为O(nlogn)
这里我们要先从步数大的开始跳,然后再慢慢缩小范围,为什么呢?
拿5举个栗子(5为当前节点离LCA的深度的距离),要向上跳5步,如果从小往大的跳:发现5 > 1 + 2 + 4,所以还要回溯一步才能得到答案,而如果我们从大往小的跳,只需for循环一次就行了:5 = 4 + 1(因为4 + 2 > 5,所以在判断时2就被排除了)
更多细节请看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll n, m, s;
const int maxn = 5e5 + 10;
int fa[maxn][22];//fa[i][j]表示节点i的第2^j级祖先
ll lg[maxn];
struct Edge {
int next, to;
}edge[maxn * 4];
int head[maxn], cnt = 0;
int dep[maxn];//dep[i]表示i节点的深度
inline void add(int x, int y) {
edge[++cnt].next = head[x];
edge[cnt].to = y;
head[x] = cnt;
}
void dfs(int x, int fath) {
dep[x] = dep[fath] + 1;//当前节点的深度为其父亲深度+1
fa[x][0] = fath;//x节点的2^0级祖先即为fa
for(int i = 1; (1 << i) <= dep[x]; i++) {
fa[x][i] = fa[fa[x][i - 1]][i - 1];
//向上更新fa值
//x节点的2^i级父亲为x节点的2^(i - 1)级父亲的2^(i - 1)父亲
//因为2^i = 2^(i - 1) + 2^(i - 1)
}
for(int i = head[x]; i; i = edge[i].next) {
int y = edge[i].to;
if(y == fath) continue;//防止往回搜索进入死循环
dfs(y, x);
}
}
int lca(int x, int y) {
if(dep[x] < dep[y]) swap(x, y);//不妨设x的深度大于y的深度
while(dep[x] > dep[y]) {
x = fa[x][lg[dep[x] - dep[y]] - 1];//先跳到同一深度
}
if(x == y) return x;//若同一深度时x是y的祖先,那么它们的lca为x或y
for(int i = lg[dep[x]] - 1; i >= 0; i--) {
if(fa[x][i] != fa[y][i]) {
x = fa[x][i], y = fa[y][i];//若向上跳后xy的父亲相等,代表找到了lca上方的节点,否则跳过去后继续找
}
}//若向上跳后父亲相等,则直到循环结束也不再移动,保留了最近lca
return fa[x][0];
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m >> s;
for(int i = 1; i <= n - 1; i++) {
int x, y;
cin >> x >> y;
add(x, y);
add(y, x);
}
dfs(s, 0);
for(int i = 1; i <= n; i++)
lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
//常数优化:先预先算出log(i) + 1的值
for(int i = 1; i <= m; i++) {
int x, y;
cin >> x >> y;
cout << lca(x, y) << endl;
}
}
/*
5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5
*/
1948

被折叠的 条评论
为什么被折叠?



