【倍增】最近共同祖先LCA
算法描述
恐龙,是指三角龙、现代鸟类和梁龙的最近共同祖先 (LCA) 及其所有后代。 ——百度百科
假设有一棵树,上面有两个节点,求两个节点最近的共同祖先节点。也可以理解为包含这两个节点的子树是从什么时候分开的。
算法思路
一生二,二生四,四生万物。 ——泥土笨笨
采用倍增的思想,将查询最近共同祖先问题分成若干部:
预处理
预处理一个数组 anc[i][j]
表示 \(i\) 节点的 \(2^j\) 号祖先的编号。其中有 anc[i][j] = anc[anc[i][j - 1]][j - 1];
意思是 \(i\) 的 \(2^j\) 祖先就是 \(i\) 的 \(2^{j-1}\) 的祖先的 \(2^{j-1}\) 祖先。通过这个式子就可以转移了。
转移过程中同时预处理 \(dep[i]\) 数组表示深度,后面会用到。
void dfs(ll u, ll father){ //该节点和它的父亲节点
dep[u] = dep[father] + 1; //预处理深度
anc[u][0] = father; //i 的 第一个祖先,即父亲。
for(ll i = 1; (1 << i) <= dep[u]; i ++){
anc[u][i] = anc[anc[u][i - 1]][i - 1]; //转移,由于是从小到大枚举的j,所以当前的anc转移一定是正确的
}
for(ll i = head[u]; i; i = pool[i].next){ //往下递归
if(pool[i].v != father){
dfs(pool[i].v, u);
}
}
}
调整深度
追平深度。顾名思义,通过不断调整深度让需要查询的两个节点深度相等。
if(dep[x] < dep[y]){
swap(x, y);
}
while(dep[x] > dep[y]){
x = anc[x][lg[dep[x] - dep[y]] - 1];
}
跳跃结算
一起往上调,跳的步数从 \(2^{dep_x}\) 步开始不断尝试,如果调完了发现重合,就放弃,然后尝试少跳一点,以此类推。直到最后到达了最接近共同祖先的位置,即共同祖先的孩子节点。为什么重合要放弃?因为谁也没办法保证当前的重合点是最近的,要不断尝试下调就太麻烦了。
if(x == y)return x;
for(ll i = lg[dep[x]]; i >= 0; i --){
if(anc[x][i] != anc[y][i]){
x = anc[x][i];
y = anc[y][i];
}
}
return anc[x][0];
总代码
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const ll MAXN = 500005;
struct Node{
ll v, next;
}pool[2 * MAXN];
ll anc[MAXN][25], dep[MAXN], head[MAXN], nn, lg[MAXN];
ll n, m, s;
void addEdge(ll u, ll v){
pool[++nn].v = v;
pool[nn].next = head[u];
head[u] = nn;
}
void dfs(ll u, ll father){
dep[u] = dep[father] + 1;
anc[u][0] = father;
for(ll i = 1; (1 << i) <= dep[u]; i ++){
anc[u][i] = anc[anc[u][i - 1]][i - 1];
}
for(ll i = head[u]; i; i = pool[i].next){
if(pool[i].v != father){
dfs(pool[i].v, u);
}
}
}
ll lca(ll x, ll y){
if(dep[x] < dep[y]){
swap(x, y);
}
while(dep[x] > dep[y]){
x = anc[x][lg[dep[x] - dep[y]] - 1];
}
if(x == y)return x;
for(ll i = lg[dep[x]]; i >= 0; i --){
if(anc[x][i] != anc[y][i]){
x = anc[x][i];
y = anc[y][i];
}
}
return anc[x][0];
}
int main(){
cin >> n >> m >> s;
for(ll i = 1; i <= n - 1; i ++){
ll x, y;
cin >> x >> y;
addEdge(x, y);
addEdge(y, x);
}
for(ll i = 1; i <= n; i ++){
lg[i] = lg[i - 1] + ((1 << lg[i - 1]) == i);
}
dfs(s, 0);
for(ll i = 1; i <= m; i ++){
ll x, y;
cin >> x >> y;
cout << lca(x, y) << "\n";
}
return 0;
}