【倍增】最近共同祖先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];

总代码

例题:洛谷 P3379【模板】最近公共祖先(LCA)

#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;
}
posted @ 2025-03-02 16:26  Allenyang_Tco  阅读(13)  评论(1编辑  收藏  举报