算法之倍增和LCA:论点与点之间的攀亲戚

前言

我们在做树形题和图论题时常常遇到这样的问题:要求求出树上两点间的最近公共祖先(LCA),这时我们该怎么办?

思路一:暴力爬爬爬……

很容易想到让两个点都往上爬,啥时候相遇了就是他们的最近公共祖先。
但是这实在是太慢了啊!怎么办呢?

思路二:倍增思想

倍增思想来源于数学。
首先,可以证明任意整数可以写成二进制形式(废话)。
然后,可以证明向上爬的层数不是正整数就是0(又是废话)。
最后,如果每次爬的层数是其二进制中的每一位,即成二的几次方的往上爬,可以证明一定能爬到他们的LCA!(恍然大悟)

如何实现

你比方说,我要向上爬个\(n\)层。这个\(n\)比如说是\(114514\)
那他的二进制就是:
\(11 011 111 101 010 010\)
我们怎么爬?
既然是成二的几次方的爬,那我们从最左边开始吧!
我们可以发现,114514的二进制最左边一位是1,代表了\(2^{17}\),二的17次方。
维护一个计数器,记录我们爬了多少。
可以发现,爬了\(2^{17}\)层后,没有超过114514,那就看下一位:\(2^{16}\)
如果这一位爬了,也是不越界的,那就继续爬
但是\(2^{15}\)这一位,如果爬了就越界了!所以此时我们要舍弃这一位,继续去看下一位能不能爬。
这就大大减少了我们向上爬的时间,这就是倍增思想。

LCA代码实现

请看代码,这同时是这道题的AC代码:

#include<bits/stdc++.h>
using namespace std;
int read(){
	int x=0;
	char c=getchar();
	while(c>'9'||c<'0'){
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		x=(x<<1)+(x<<3)+(c^'0');
		c=getchar();
	}
	return x;
}
int n,m,s,x,y;
vector<int>tree[500001];
int dep[500001];
int f[500001][21];
void dfs(int now,int d,int father){
	dep[now]=d;
	f[now][0]=father;
	for(int i=1;i<=20;i++){
		f[now][i]=f[f[now][i-1]][i-1];
	}
	for(int i=0;i<tree[now].size();i++){
		if(tree[now][i]!=father)
			dfs(tree[now][i],d+1,now);
	}
}
int lca(int u,int v){
	if(dep[u]<dep[v])swap(u,v);
	
    for(int i=20;i>=0;i--)
    	if(f[u][i]&&dep[f[u][i]]>=dep[v])
    		u=f[u][i];
	
	if(u==v)return u;
	
	for(int i=20;i>=0;i--){
		if(f[u][i]&&f[v][i]&&f[u][i]!=f[v][i]){
			u=f[u][i];
			v=f[v][i];
		}
	}
	return f[u][0];
}
int main(){
	n=read();
	m=read();
	s=read();
	for(int i=1;i<n;i++){
		x=read();
		y=read();
		tree[x].push_back(y);
		tree[y].push_back(x);
	}
	dfs(s,1,0);
	for(int i=1;i<=m;i++){
		x=read();
		y=read();
		printf("%d\n",lca(x,y));
	}
	return 0;
} 
posted @ 2023-01-15 16:47  MornHus  阅读(65)  评论(0)    收藏  举报