[笔记]倍增求LCA
原题链
算法描述
LCA是指两个点的最近公共祖先.
在程序中设f[x][k]
表示x的 2^k 辈祖先,即从x向上(根节点)走 2^k 步所到达的节点,如果向上走到的节点不存在则令f[x][k] = 0
,并且f[x][0]
为x节点的父节点,因为 2^0 = 1,且任意一个节点向上走一步到达的就是它的父节点.同时我们会发现一个性质:∀k∈[1,log(n)],f[x][k] = f[f[x][k - 1][k - 1]]
,这个式子的意思是x向上跳 2^k 步所到达的节点与x向上跳 2^(k-1) 步所到达的节点再向上跳 2^(k-1) 步所到达的节点是相同的,可以用同底数幂相加的原理证明(a^b + a^c = a^(b+c)).以上为预处理部分.
如何求LCA:设dis[x]为x节点的深度,并规定dis[x] ≥ dis[y]如果不满足这个条件则交换x,y;此时我们保证了x的深度一定大于y,所以我们先尝试将x向上跳 2^(logn) 步,…跳 2^1 步,跳 2^0 步,并检查所跳到的节点是否仍比y深,如果仍比y深则继续上跳.如果调到x==y
则说明x,y已经到达了两者的LCA处,直接输出.但如果当两者的深度一致时x,y仍不相等,此时将x,y同时上跳直到f[x][i] != f[y][i]
,由于我们跳的步幅是从大到小的,所以一开始跳可能回调到两点的lca的祖先节点,这很明显不是答案,所以我们要缩小步伐,缩小到什么时候呢?应该是第一次两点向上跳到的节点不是同一个的时候,我们设这个节点为p,此时p节点的父节点(这个父节点不是通常意义上的高一级的直属父亲,而是倍增意义下跳 2^i 步到达的节点)即为x,y的LCA.算法结束,具体见程序
AC代码
#include <bits/stdc++.h>
using namespace std;
struct node{
int to,next;
}edge[1000010];
int fir[1000010],n,m,s,tot,t,dis[500010],f[500010][35];
void add(int x,int y){//前向星存边
tot++;
edge[tot].to = y;
edge[tot].next = fir[x];
fir[x] = tot;
}
void bfs(){//预处理出每个点的深度,并求出f数组
memset(dis,0,sizeof(dis));
queue < int > q;
while(!q.empty())q.pop();
dis[s] = 1;
q.push(s);
while(!q.empty()){
int x = q.front();
q.pop();
for(int i = fir[x];i;i = edge[i].next){
if(dis[edge[i].to] != 0)continue;
f[edge[i].to][0] = x;
dis[edge[i].to] = dis[x] + 1;
for(int j = 1;j <= t;j++){
f[edge[i].to][j] = f[f[edge[i].to][j - 1]][j - 1];
}
q.push(edge[i].to);
}
}
return;
}
int lca(int x,int y){
if(dis[x] < dis[y])swap(x,y);
if(x == y)return x;
for(int i = t;i >= 0;i--){
if(dis[f[x][i]] >= dis[y])x = f[x][i];
}
if(x == y)return x;
for(int i = t;i >= 0;i--){
if(f[x][i] != f[y][i]){
x = f[x][i];
y = f[y][i];
}
}
return f[x][0];
}
int main(){
scanf("%d%d%d",&n,&m,&s);
for(int i = 1;i < n;i++){
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
t = (int)(log(n) / log(2)) + 1;
bfs();
for(int i = 1;i <= m;i++){
int x,y;
scanf("%d%d",&x,&y);
printf("%d
",lca(x,y));
}
return 0;
}