,https://www.luogu.org/problem/show?pid=3379
最近公共祖先方法有很多的,现在我们利用倍增表求lca
何为倍增表,简单的说是成倍增加表
bz[i][j]表示在第i位上向前推进2^j步
对于一颗有根树,bz[i][j]表示从第i为向根节点走2^j步,就是说高度增加2^j
倍增表功能强大,这里不展开(我不会…)
首先我们讲一下倍增
它的地推公示bz[i][j]=bz[bz[i][j-1]][j-1]
和st表很像的
对于一棵树,我们可以dfs求出bz[i][0]
就是i节点的父节点2^0=1;
然后
for(int j=1;n>=(1<<j);j++)
for(int i=1;i<=n;i++)
if(bz[i][j-1])
bz[i][j]=bz[bz[i][j-1]][j-1];
在爆搜的同时,我们直接求出deep[i],即深度
然后每读入两个数x,y(deep[y]>=deep[x]),如果他们有高度差,先把较深的点顺着根节点向上爬,爬到两个点相同高度;
首先我们搞一个j=0;不断增加j,使deep[bz[y][j]]>deep[x]
那么deep[bz[y][j-1]]一定小于deep[x]且距离deep[x]较近
显然因为y和bz[y][j-1]都在x的下面所以他们公共最近祖先是一样的
那我们把y提升到bz[y][j-1]的位置
继续重复,直到deep[x]==deep[y];
当然咯j不断增加,有可能bz[y][j]比根节点还大,但这时deep[bz[y][j]]是0,所以不会对答案有影响;
提升到同一高度后,我们就可以同时提升xy,方法和上面一样
直到x==y当然,每个节点本身也是自己的祖先
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
struct cs{
int to,next;
}a[2000004];
int head[600005],deep[600005],bz[600001][25];
int p,n,m,x,y,z,ll;
void inc(int x,int y){
ll++;
a[ll].to=y;
a[ll].next=head[x];
head[x]=ll;
}
void dfs(int x,int y,int z){
bz[x][0]=y;
deep[x]=z;
int k=head[x];
while(k){
if(a[k].to!=y) dfs(a[k].to,x,z+1);
k=a[k].next;
}
}
int upone(int stdd,int x){//把两个点提升到相同高度
while(deep[x]!=stdd){
int j=0;
while(deep[bz[x][j]]>=stdd)j++;
x=bz[x][j-1];
}
return x;
}
int happytogether(int x,int y){//两个点一起提升,只现在更新x点,从y点过来
if(x==y)return x;
while(1){
int j=0;
if(bz[x][j]==bz[y][j])return bz[x][j];
while(bz[x][j]!=bz[y][j])j++;
j--; x=bz[x][j]; y=bz[y][j];
}
}
int lca(int x,int y){
if(deep[x]>deep[y])swap(x,y);
y=upone(deep[x],y);
return(happytogether(x,y));
}
int main()
{
scanf("%d%d%d",&n,&m,&p);
for(int i=1;i<=n-1;i++){
scanf("%d%d",&x,&y);
inc(x,y);
inc(y,x);
}
dfs(p,-6,1);
for(int j=1;n>=(1<<j);j++)
for(int i=1;i<=n;i++)
if(bz[i][j-1])
bz[i][j]=bz[bz[i][j-1]][j-1];
while(m--){
scanf("%d%d",&x,&y);
printf("%d\n",lca(x,y));
}
}