一、LCA 是什么
最近公共祖先是指对于有根树T的两个结点
如下图所示,当
的祖先有: 、 、 。
的祖先有: 、 、 、 。
它们的公共祖先分别有 (虽然说肉眼也能看出来吧) 。

二、LCA 如何实现
1. 枚举法:
把
2. 倍增法
(1). 思路
先把要查询的
(2). 做法
①. 建图
我们需要采用一种容器来存储这张树,在这里选择 链式前向星 的原因,是其优秀的 时间复杂度 与 空间复杂度,能够容纳下大量的数据
struct EDGE{
int v,next;//v表示编号为i的边的终点,next表示以第i条边的起点为起点的下一条边的编号。
}edge[N];
int head[N],idx=1;//head[i]表示以i为起点的最后一条加入的边的编号。
void add_edge(int u,int v){
edge[idx].v=v;//将终点设置为终点。
edge[idx].next=head[u];//第idx条边的下一条边的编号即为以u为起点的加入的最后一条边的编号。
head[u]=idx;//以u为起点的最后一条加入的边更新。
idx++;//编号跳到下一条边。
}②. 确定深度与每步跳到的点位
确定了容器之后,我们就要确定每一个点的 深度 与它跳
首先,我们 确定一个根节点 作为整棵树的根,然后用深搜算法遍历这颗树的每一个节点,一路一边将他们的层数设置为他们的祖先的上一层,一边用状态转移方程将他们跳
若用
若用
void dfs(int u,int fa){
dep[u]=dep[fa]+1;
for(int i=head[u];i;i=edge[i].pre){//从以u为起点的加入的最后一条边开始遍历,遍历所有以u为起点的边。
int j=edge[i].v;//取出当前边的终点编号。
if(j==fa)continue;//如果当前这条边的终点是上一条边的起点,那么说明这其实是一条无向边添加进edge数组的两条边,不执行。
f[j][0]=u;//跳2^0步到达的点即为u。
for(int k=1;(1<<k)<=dep[u];k++){
f[j][k]=f[f[j][k-1]][k-1];//状态转移
}
dfs(j,u);//继续搜索下一层
}
}③. 倍增找公共祖先
设要寻找公共祖先的两个点的编号分别为
第一步:将
换为深度较大的一个,方便操作。
第二步:按照
的次幂的步数枚举,将 跳至与 同等的深度。
第三步:让
与 共同前进,但是不能让它们跳到它们的公共祖先上。
第四步:此时,
与 都会恰恰位于它们最近公共祖先的下一层,因此只需要让它们再跳 步即可。
代码如下所示
int lca(int x,int y){
//第一步
if(dep[x]<dep[y])swap(x,y);
//第二步
for(int i=19;i>=0;i--){
if(dep[f[x][i]]>=dep[y]){
x=f[x][i];
}
}
//若此时已经找到祖先了,就return。
if(x==y)return y;
//第三步
for(int i=19;i>=0;i--){
if(f[x][i]!=f[y][i]){
x=f[x][i];
y=f[y][i];
}
}
//第四步
return f[y][0];
}3. Tarjan 法
我不会QAQ
为什么用 Tarjan,倍增怎么你了
想要极致的时间复杂度还得人造 vector
趁朱老师发现之前,开溜
三、例题
1.【模板】最近公共祖先(LCA)
| 平台 | 网址 |
|---|---|
| 洛谷 | https://www.luogu.com.cn/problem/P3379 |
| 模板题,直接上代码: |
#include <bits/stdc++.h>
using namespace std;
const int N=5e5+10;
struct EDGE{
int v,next;
}edge[N*2];
int n,m,root,head[N],dep[N],f[N][20],id=1;
void un(int u,int v){
edge[id].v=v;
edge[id].next=head[u];
head[u]=id;
id++;
}
void dfs(int u,int fa){
dep[u]=dep[fa]+1;
for(int i=head[u];i;i=edge[i].next){
int j=edge[i].v;
if(j==fa)continue;
f[j][0]=u;
for(int k=1;(1<<k)<=dep[u];k++){
f[j][k]=f[f[j][k-1]][k-1];
}
dfs(j,u);
}
}
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
for(int i=19;i>=0;i--){
if(dep[f[x][i]]>=dep[y]){
x=f[x][i];
}
}
if(x==y)return y;
for(int i=19;i>=0;i--){
if(f[x][i]!=f[y][i]){
x=f[x][i];
y=f[y][i];
}
}
return f[y][0];
}
int main(){
scanf("%d%d%d",&n,&m,&root);
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
un(u,v);
un(v,u);
}
dfs(root,0);
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
printf("%d\n",lca(x,y));
}
return 0;
}