最近公共祖先LCA 倍增法

8 篇文章 3 订阅
1 篇文章 0 订阅

 之前学习了Tanjan求LCA的解法,

最近公共祖先(LCA)Tarjan_Brokenrivers的博客-CSDN博客


这次学习一下倍增法

倍增基于一个常识,任何一个数都能表示为2的幂之和的形式如

9=2^3+2^0=1001

也就是二进制的形式,基于此,对于线性的过程,则可以通过2^x的跳跃实现log级别的复杂度。

最暴力的LCA做法就是手动将两个节调整到同一高度,然后同时向上跳直到取到最近的公共祖先。

通过倍增预处理一个跳转表 f [i][j]表示i节点的第2^j个父亲

之后的跳转就只需通过从这个表中取值就行了。

#include<bits/stdc++.h>
using namespace std;
#pragma warning(disable:4996);
#define int long long 
#define rep(j,b,e) for(int j=(b);j<=(e);j++)
#define drep(j,e,b) for(int j=(e);j>=(b);j--)
const int N = 5e5 + 10;
int T = 1;
int n, m, k;
int qpow(int x, int y) {//快速幂
	int ans = 1;
	while (y) {
		if (y % 2 == 1)ans *= x;
		x *= x;
		y /= 2;
	}
	return ans;
}
vector<int>gra[N];
int dep[N];
int fathers[N][35];//i节点的第2^j个父亲
int maxDep = 0;//最大深度
void dfs(int now, int fa) {//预处理LCA跳转表
	dep[now] = dep[fa] + 1;
	maxDep = max(maxDep, dep[now]);
	for (int i = 1; qpow(2, i) <= dep[now]; i++) {
		fathers[now][i] = fathers[fathers[now][i - 1]][i - 1];
		//i节点向上跨2^j步等于先跨2^j-1再跨2^j-1
	}
	for (auto nx : gra[now]) {
		if (nx != fa) {//处理直接回边
			fathers[nx][0] = now;//递推初始化边界
			dfs(nx, now);
		}
	}
}
int LCA(int a, int b) {
	if (dep[a] < dep[b])swap(a, b);//a为深度最大节点
	int s = (log(dep[a]) / log(2));
	for (int i = s+1; i >= 0; i--) {//枚举低的点跳转高度,使ab两点为同一高度
		if (dep[fathers[a][i]] >= dep[b])a = fathers[a][i];
		//等于的时候停止更新
		if (b == a)return a;
		//特判如果同一深度时刚好是一个点,说明公共祖先为其中的一点
	}
	for (int i = s+1; i >= 0; i--) {//同一高度后,一起向上跳直到父亲为同一点,返回
		if (fathers[a][i] != fathers[b][i]) {
			a = fathers[a][i];
			b = fathers[b][i];
		}
	}
	return fathers[a][0];
}
signed main() {
#ifndef ONLINE_JUDGE
	freopen("out.txt", "w", stdout);
#endif
	ios::sync_with_stdio(0); cout.tie(0);
	cin >> n >> m >> k;
	rep(j, 1, n - 1) {
		int f, t;
		cin >> f >> t;
		gra[f].push_back(t);
		gra[t].push_back(f);
	}
	dfs(k, 0);//预处理
	rep(j, 1, m) {
		int a, b;
		cin >> a >> b;
		cout << LCA(a, b) << endl;
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值