算法之倍增和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;
}

浙公网安备 33010602011771号