最近公共祖先

一、概念

在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先,就是:

两个节点在这棵树上深度最大的公共的祖先节点。

二、暴力求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
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值