一.概述
这种描述是基于树结构的。树实际上就是图论中的有向无环图。
而要研究LCA问题,首先我们要指定树中的一个顶点为根节点,并以该节点遍历有向无环图,生成一颗DFS序下的树。
假设我们要查询的两个节点为u和v,DFS序下根节点到两点的最短路径分别是(r,u),和(r,v),LCA就是(r,u)与(r,v)公共路径的最后一个节点,如下图所示,w即为LCA。
二.具体实现
首先,我们来看一道题,我们将采用三种方法来解题。
1.暴力
主要思想:通过不断地向上求父节点,直到两个点的父节点重合时,即可得到LCA。
我们需要三个数组来维护数据:
- flag[N]数组,使用dfs构建这棵树时,用于标识是否已经访问过这个节点。
- father[N]数组,用来标记每个节点的父节点。
- depth[N]数组,用来记录每个节点的深度。
需要vector容器来记录边的信息。
//小明的族谱 暴力
#include <iostream>
#include <vector>
using namespace std;
const int N=5e5+10;
int n,q;
int depth[N];
int father[N];
bool flag[N]={false};
vector<int> edge[N];
void dfs(int start,int deep){
flag[start]=true;
depth[start]=deep;
for(int i=0;i<edge[start].size();i++){
if(!flag[edge[start][i]]){
father[edge[start][i]]=start;
dfs(edge[start][i],deep+1);
}
}
return;
}
int lca(int index1,int index2){
//深度对齐
while(depth[index1]>depth[index2]){
index1=father[index1];
}
while(depth[index2]>depth[index1]){
index2=father[index2];
}
while(index1!=index2){
index1=father[index1];
index2=father[index2];
}
return index1;
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
scanf("%d%d",&n,&q);
for(int i=1;i<n;i++){
int x,y;
scanf("%d%d",&x,&y);
edge[x].push_back(y);
edge[y].push_back(x);
}
dfs(1,1);
for(int i=0;i<q;i++){
int u,v;
scanf("%d%d",&u,&v);
printf("%d\n",lca(u,v));
}
return 0;
}
2.跳表(倍增)
首先,我们需要对齐待查询的两个节点的深度。待查询的两个节点深度一致以后,再一起向上跳,但是这个上跳有所不同,是按照2的幂次进行跳。
因为对于任意一个整数,都可以用二进制进行表示。数的二进制划分体现了一种快速增长的特性。
一个整数n,它的二进制表示只有位。如果要从0增长到n,可以用1、2、4、……、为跳板,快速跳到n,这些跳板只有个。
我们需要两个数组来维护数据:
- depth[N] 用来记录每个节点的深度
- father[N][20] 用来跳表使用的,father[i][0]记录的是i节点的父节点,层次上差了一层,father[i][1]层次上差了两层(与father[i][0]),这样的结构能减少复杂度。
//小明的族谱 跳表
#include <iostream>
#include <vector>
using namespace std;
const int N=5e5+10;
int n,q;
int depth[N];
int father[N][20];
vector<int> edge[N];
void dfs(int start,int parent){
depth[start]=depth[parent]+1;
father[start][0]=parent;
//这里记录的父节点,跳的是2^i
for(int i=1;i<20;i++){
father[start][i]=father[father[start][i-1]][i-1];
}
for(int i=0;i<edge[start].size();i++){
if(edge[start][i]!=parent){
dfs(edge[start][i],start);
}
}
}
int lca(int index1,int index2){
if(depth[index1]<depth[index2]){
swap(index1,index2);
}
for(int i=19;i>=0;i--){
if(depth[father[index1][i]]>=depth[index2]){
index1=father[index1][i];
}
}
if(index1==index2){
return index1;
}
for(int i=19;i>=0;i--){
if(father[index1][i]!=father[index2][i]){
index1=father[index1][i];
index2=father[index2][i];
}
}
return father[index1][0];
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
scanf("%d%d",&n,&q);
for(int i=1;i<n;i++){
int x,y;
scanf("%d%d",&x,&y);
edge[x].push_back(y);
edge[y].push_back(x);
}
depth[1]=0;
dfs(1,1);
for(int i=0;i<q;i++){
int u,v;
scanf("%d%d",&u,&v);
printf("%d\n",lca(u,v));
}
return 0;
}