题目
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
题解
树上倍增和普通的倍增原理是一样的,它的运用很广泛,除了求LCA外,在很多问题中都有应用
倍增就是将状态空间中2的整数次幂的值作为代表,当要查询其它位置的值时,可以通过“任意整数可以表示成若干个2的次幂项的和”这一性质,使用之前求出的代表值拼成所需的值。
在树上倍增求LCA中,设f[i][k]表示点i的2^k辈父亲,而f[i][0]表示i的父节点。通过一个广搜或深搜预处理出所有节点的f,还有节点的深度。其中f[i][k]=f[f[i][k-1]][k-1],1<=k<=log(n)/log(2)+1
求LCA时,先把两个节点弄到同一高度,然后一起往上跳,最终跳到的一定是它们最近公共祖先的子节点,答案就得出了(详见注释)
代码
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <queue>
using namespace std;
const int N=500005;
int n,m,s,t,cnt;
int ls[N],ne[N*2],to[N*2],b[N];
int f[N][22];
queue<int> q;
void bfs(){
q.push(s);b[s]=1;f[s][0]=s;
while (q.size()){
int k=q.front(),e;
q.pop();
e=k;
for (k=ls[k];k;k=ne[k]){
if (b[to[k]]) continue;
b[to[k]]=b[e]+1;
f[to[k]][0]=e;
for (int i=1;i<=t;i++)
f[to[k]][i]=f[f[to[k]][i-1]][i-1];
q.push(to[k]);
}
}
}
int lca(int c,int d){
if (b[c]>b[d]) swap(c,d);//比较的是深度,不是点的标号!!!!
for (int i=t;i>=0;i--)
if (b[f[d][i]]>=b[c]) d=f[d][i];//把深度大的跳到和深度小的同一深度
//只在d的父亲的深度大于等于深度小的c时跳,这样就不会跳过头,通过2的次幂组合可以刚好跳到
if (c==d) return c;//当c刚好就是LCA时
for (int i=t;i>=0;i--)
if (f[c][i]!=f[d][i]) c=f[c][i],d=f[d][i];
//一起跳,只在两点父辈不同时跳,可以通过2的次幂项组合跳到LCA的儿子节点
return f[c][0];
}
int main(){
scanf("%d%d%d",&n,&m,&s);
t=(int)(log(n)/log(2))+1;
for (int i=1;i<n;i++){
int a,b;
scanf("%d%d",&a,&b);
ne[++cnt]=ls[a];ls[a]=cnt;to[cnt]=b;
ne[++cnt]=ls[b];ls[b]=cnt;to[cnt]=a;
}
bfs();
for (int i=1;i<=m;i++){
int a,b;
scanf("%d%d",&a,&b);
printf("%d\n",lca(a,b));
}
}