之前学习了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;
}
}