LCA
定义
LCA:求最近公共祖先,是一个基本的树上问题
首先给出一些定义
公共祖先:在一颗有根树上,若F是x的祖先,同时也是y的祖先,则F为x,y的公共祖先
最近公共祖先:x,y的公共祖先中深度最大的
如何求
简单的方法:分别从x,y出发向根节点走,打上标记,第一次相遇的节点就是LCA(x,y)
复杂度O(nm),效率太低
常用方法:倍增,Tarjan,树链剖分
本节讲倍增(在线算法),Tarjan是离线算法,复杂度O(n+m),是最优的,但本蒟蒻不会,树剖法会在树链剖分那里讲
实现
上面一步一步走太慢,考虑跳,按 2 的倍数向上跳,即倍增法
总的分为两个步骤
步骤一
把x,y跳到同一高度
即让x跳到y的高度(默认x为较深的节点)
但我们只知道每个节点的父亲节点,只能一步一步往上走
因此,需要预处理出每个节点的祖先节点,作为x的跳板
而倍增法只需预处理出x的 2 的倍增的祖先(第1,2,4,8,16……个祖先)
如何快速预处理?
定义 fa[x][i] 为 x 的第 2^i 个祖先
则 fa[x][0] 为 x 的第 2^0 个祖先,即 x 的父亲节点
则有下面的递推式:
fa[x][i]=fa[fa[x][i-1]][i-1]
里面的 fa[x][i-1] : 从 x 起跳,跳 2i-1 步,到了 z 节点,z=fa[x][i-1]
外面的 fa[x][i-1] : 从 z 起跳,跳 2i-1 步,到了 z1 节点, z1=fa[z][i-1]
2i-1+2i-1=2i 所以递推式右边的就等价于从 x 起跳,跳 2i 步,也就是左边的 fa[x][i]
从任意节点到根节点,最多有 logn 个 fa[x][] ,所以只需递推 O(logn) 次,计算 n 个节点,共计算 O(nlogn) 次
void dfs(int x,int father){//预处理各节点的深度和倍增祖先
deep[x]=deep[father]+1;
fa[x][0]=father;
for(int i=1;(1<<i)<=deep[x];i++){
fa[x][i]=fa[fa[x][i-1]][i-1];
}
for(int i=head[x];~i;i=e[i].nxt){
if(e[i].to!=father){
dfs(e[i].to,x);
}
}
}
if(deep[x]<deep[y])swap(x,y);//让x成为较深的那个节点
//把x提到与y相同的深度
for(int i=19;i>=0;i--){
if(deep[x]-(1<<i)>=deep[y])
x=fa[x][i];
}
步骤二
让 x,y 同步往上跳
从一个节点跳到根节点最多要跳 logn 次,从 x,y 出发,跳到祖先 fa[x][i] ,fa[y][i] 有两种情况:
fa[x][i] == fa[y][i]
这是一个公共祖先,深度<= LCA(x,y), 这说明跳过头了,要换一个小的 i-1
fa[x][i] != fa[y][i]
还没跳到公共祖先,更新 x=fa[x][i] y=fa[y][i] 从新的 x,y 开始跳
当 i 减到 0 ,x,y 正好位于 lca 的下一层, fa[x][0] 即 lca(x,y)
查询一次 lca 循环 logn 次 ,复杂度 O(logn)
//一起跳
if(x==y)return y;
for(int i=19;i>=0;i--){
if(fa[x][i]!=fa[y][i]){
x=fa[x][i],y=fa[y][i];
}
}
return fa[x][0];
总代码
#include <bits/stdc++.h>
using namespace std;
const int N=500005;
int n,m,s;
int deep[N],fa[N][20];
struct node{
int to,nxt;
}e[N*2];
int head[N*2];
int cnt;
void add(int u,int v){
e[cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt++;
}
void dfs(int x,int father){//预处理各节点的深度和倍增祖先
deep[x]=deep[father]+1;
fa[x][0]=father;
for(int i=1;(1<<i)<=deep[x];i++){
fa[x][i]=fa[fa[x][i-1]][i-1];
}
for(int i=head[x];~i;i=e[i].nxt){
if(e[i].to!=father){
dfs(e[i].to,x);
}
}
}
int lca(int x,int y){
if(deep[x]<deep[y])swap(x,y);//让x成为较深的那个节点
//把x提到与y相同的深度
for(int i=19;i>=0;i--){
if(deep[x]-(1<<i)>=deep[y])
x=fa[x][i];
}
//一起跳
if(x==y)return y;
for(int i=19;i>=0;i--){
if(fa[x][i]!=fa[y][i]){
x=fa[x][i],y=fa[y][i];
}
}
return fa[x][0];
}
int main(){
for(int i=0;i<N*2;i++){
e[i].nxt=-1;
head[i]=-1;
}
cin>>n>>m>>s;
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
add(u,v),add(v,u);
}
dfs(s,0);
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
cout<<lca(x,y)<<endl;
}
return 0;
}