[C++]P3379 LCA 最近公共祖先

最近公共祖先 LCA 倍增写法

LCA的倍增主要由三个重要的过程组成

预处理lg数组

DFS求fa depth

倍增节点

观看以下内容前建议先把完整代码大致纵览一遍,有利于理解各个函数的意义

倍增思想

暴力解决LCA是通过 x 和 y 一个一个的往上跳

而倍增的思想是希望节点能够一次性尽可能的多跳

并且可以通过一个有序数列来控制跳的层数

1 2 4 8 16 32 ...

\(2^0\) \(2^1\) \(2^2\) \(2^3\) \(2^4\) \(2^5\) ...

(可以有机联想10进制到2进制的转化)

比如: 当前需要跳35层

35 - 32 = 3

3 - 2 = 1

1 - 1 = 0

这样就将原本需要跳35次压缩到只需要跳3次

那如何知道应该跳几次呢?

这时候就要通过lg数组来实现

预处理lg数组

lg数组的含义: \(lg[i]\) 代表深度为i的节点一次性可以往上最多跳 2(lg[i]-1) 个节点

在 Read() 中,我们可以找到如下代码:

	for(int i = 1;i <= n;i++){
		lg[i] = lg[i-1] + (1 << lg[i-1] == i);
	}

为了理解这段话的意思 我们先把这个处理后的数组 lg[0 - 100] 打印出来:

0 1 2 2 3 3 3 3 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 

比如: 当预期要跳的深度为35 那就看 lg[35] = 6

则需要跳的次数为 26-1 = 32

所以最多跳32层

其余也是同理

DFS求fa depth

Code

void dfs(int u,int f){
	fa[u][0] = f;
	depth[u] = depth[f] + 1;
	for(int i = 1;i <= lg[depth[u]];i++)
		fa[u][i] = fa[fa[u][i-1]][i-1];
	for(int i = head[u];i;i = e[i].u){
		if(e[i].v != f) dfs(e[i].v,u);
	}
}

求depth

我们都知道DFS是从上往下搜的

可以通过父节点的depth+1来得到当前节点的depth

因此处理depth只需要将父节点的值加1即可:

	depth[u] = depth[f] + 1;

求fa

要求fa数组要先搞懂其含义:\(fa[i][j]\) 指 节点 \(i\)\(2^j\)级祖先

由于 \(2^j\) = 2j-1 + 2j-1

所以 \(i\) 的 2j-1 级祖先的 \(j-1\) 级祖先就是 \(i\)\(j\) 级祖先:

	fa[u][i] = fa[fa[u][i-1]][i-1];

既然要通过前面的fa求后面的fa

那必须将\(fa[i][0]\)求出 既 \(i\) 的父节点:

	fa[u][0] = f;

倍增节点

Code

int LCA(int x,int y){
	if(depth[x] < depth[y]) swap(x,y);//让 节点x 作为较深的节点
	while(depth[x] > depth[y]){//把 x 和 y 放至相同深度
		x = fa[x][lg[depth[x]-depth[y]] - 1];
	}
	if(x == y) return y;/*
		如果 x 被拉到 y 上
		说明 y 就是 x 的祖先
		因此直接返回 y
	*/
	for(int k = lg[depth[x]] - 1;k >= 0;k--){
		if(fa[x][k] != fa[y][k])//如果 x 和 y 没有相遇 则说明可以倍增到这个位置
			x = fa[x][k],y = fa[y][k];
	}
	return fa[x][0];//返回 x 的父亲节点
}

注释中写道 如果 x 和 y 没有相遇 则说明可以倍增到这个位置

这是什么意思?

我们不妨设想如果没有这个if()
直接相遇就break;

那就会发生非最近公共祖先的问题(就是倍增过头了)

因此一直倍增却不允许相遇 就会让他们停在LCA的下一层

这一部分比较简单 所以就直接把注释打在代码上了

完整代码

//P3379 注释版
#include<bits/stdc++.h>
#define maxn 500010
using namespace std;

int head[maxn],cnt;
struct tree{
	int u,v;
	tree(int a = 0,int b = 0){
		u = head[a];
		v = b;
	}
}e[maxn << 1];
void add(int u,int v){
	e[++cnt]=tree(u,v);
	head[u] = cnt;
	e[++cnt]=tree(v,u);
	head[v] = cnt;
}//邻接表

int depth[maxn],fa[maxn][22],lg[maxn];
int m,n,s;
/*
	定义变量
	depth[i] 节点i的深度
	fa[i][j] 节点i的2^J级祖先
	lg[i] 辅助参数
*/

void Read(){//输入
	cin >> n >> m >> s;
	int x,y;
	for(int i = 1;i < n;i++){
		cin >> x >> y;
		add(x,y);
	}
	for(int i = 1;i <= n;i++){
		lg[i] = lg[i-1] + (1 << lg[i-1] == i);
	}/*
		常数优化
		lg[i]代表深度为i的节点可以往上最多跳2^(lg[i] - 1)个节点
	*/
}

void dfs(int u,int f){
	fa[u][0] = f;
	depth[u] = depth[f] + 1;
	for(int i = 1;i <= lg[depth[u]];i++)
		fa[u][i] = fa[fa[u][i-1]][i-1];
	for(int i = head[u];i;i = e[i].u){
		if(e[i].v != f) dfs(e[i].v,u);
	}
}/*
	DFS
	可以求出 fa depth
*/

int LCA(int x,int y){
	if(depth[x] < depth[y]) swap(x,y);//让 节点x 作为较深的节点
	while(depth[x] > depth[y]){//把 x 和 y 放至相同深度
		x = fa[x][lg[depth[x]-depth[y]] - 1];
	}
	if(x == y) return y;/*
		如果 x 被拉到 y 上
		说明 y 就是 x 的祖先
		因此直接返回 y
	*/
	for(int k = lg[depth[x]] - 1;k >= 0;k--){
		if(fa[x][k] != fa[y][k])//如果 x 和 y 没有相遇 则说明可以倍增到这个位置
			x = fa[x][k],y = fa[y][k];
	}
	return fa[x][0];//返回 x 的父亲节点
}

int main(){
	Read();
	dfs(s,0);
	for(int i = 1;i <= m;i++){
		int x,y;
		cin >> x >> y;
		cout << LCA(x,y) <<endl;
	}
	return 0;
}
posted @ 2021-03-21 09:40  Rosyr  阅读(432)  评论(0编辑  收藏  举报