LCA(Least Common Ancestor),顾名思义,是指在一棵树中,距离两个点最近的两者的公共节点。也就是说,在两个点通往根的道路上,肯定会有公共的节点,我们就是要求找到公共的节点中,深度尽量深的点。
还可以表示成另一种说法,就是如果把树看成是一个图,这找到这两个点中的最短距离。
1.dfs序+RMQ求LCA
/*
用dfs序和时间戳
如果求m,n的LCA 记录m,n第一次出现的时间
在时间之内 找到一点有最小深度 此点就是lca
样例
5 4
1 2
1 3
2 4
2 5
4 5
*/
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define M 1000
#define N 1000
using namespace std;
int n,m;int num=0;
int b[M],nt[M],p[N];
int a[N],height[N],ttime=0,first[N],minnum[N][N];
void insert(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
num++;
b[num]=y;
nt[num]=p[x];
p[x]=num;
num++;
b[num]=x;
nt[num]=p[y];
p[y]=num;
}
}
void dfs(int x,int h){
++ttime;
a[ttime]=x;
height[ttime]=h;
if(!first[x])
first[x]=ttime;
for(int e=p[x];e;e=nt[e])
{
int k=b[e];
if(!first[k]) dfs(k,h+1);
++ttime;
a[ttime]=x;
height[ttime]=h;
}
}
void RMQ()//用RMQ求height中最小的结点
{//此时minnum中存的是最小的height对应的编号
for(int i=1;i<=ttime;i++)
minnum[i][0]=i;
int t=log2(ttime);
for(int k=1;k<=t;k++){
for(int i=1; i<=ttime;i++)
{
if(height[minnum[i][k-1]]<=height[minnum[i+(1<<(k-1))][k-1]])//比较左区间最小的height对应的编号对应的height(就是最小的height)与右区间的......
minnum[i][k]=minnum[i][k-1];
else minnum[i][k]=minnum[i+(1<<(k-1))][k-1];//一定是1<<(k-1)不是k!!!
}
}
}
int lca(int x,int y){
if(x>y) swap(x,y);
int k;
int t=log2(ttime);
if(height[minnum[x][t-1]]<height[minnum[y-t+1][t-1]])
k=minnum[x][t-1];
else k=minnum[y-t+1][t-1];
//printf("k=%d\n",k);
return a[k];
}
int main(){
insert();
dfs(1,0);
RMQ();
int x,y;
scanf("%d%d",&x,&y);
printf("%d\n",lca(first[x],first[y]));
return 0;
}
2.倍增法求LCA(在线做法) 详细见注释
/*
样例:
10 9
1 2
1 3
2 4
2 5
3 6
5 7
5 8
7 9
7 10
9 6
(求9,6的lca)
思路 两步
1.将深度大的点翻到与深度小的点同一高度
2.两个点一起上翻 找lca
预处理fa二维数组 d一维数组
fa[x][y] 表示顶点x向上走2^y个顶点到达的点 d[x]数组是每个点的深度
*/
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define M 1000
#define N 1000
using namespace std;
int n,m;int num=0;
int b[M],w[M],nt[M],p[N];
int fa[M][20],d[M];
void insert(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
num++;
b[num]=y;
nt[num]=p[x];
p[x]=num;
num++;
b[num]=x;
nt[num]=p[y];
p[y]=num;
}
}
void dfs(int x){
for(int i=1;i<=20;i++){//这里的20视题而定 这里表示最大深度为2^20
if(d[x]<(1<<i))break;
fa[x][i]=fa[fa[x][i-1]][i-1];
}//求fa数组的关键 和RMQ类似
for(int i=p[x];i>0;i=nt[i]){
int v=b[i];
if(v==fa[x][0]) continue;//无向边 不走已经走过的父节点
fa[v][0]=x;
d[v]=d[x]+1;//求深度=父节点深度+1
dfs(v);
}
}
int lca(int x,int y){
int h;
if(d[x]<d[y]) swap(x,y);
for(h=0;(1<<h)<=d[x];h++);
h--;
for(int i=h;i>=0;i--){
if(d[x]-(1<<i)>=d[y])
x=fa[x][i];
}//使a b深度相等
/*
或者可以写成
int t=d[x]-d[y];
for(int i=0;i<=20;i++)
{
if(t&(1<<i))
x=fa[x][i];
}
利用位运算 20的解释同上
*/
if(x==y) return x;//如果相等直接return
for(int i=h;i>=0;i--){//找lca 从大到小找(图中就是从上往下) lca是最下面的 公共父节点
if(fa[x][i]!=fa[y][i]){//关键:如果 不相等 fa[x][i]和fa[y][i]一定在lca的下面一个位置
x=fa[x][i];
y=fa[y][i];
}
}
return fa[x][0];//lca
}
int main(){
insert();
dfs(1);
int x,y;
scanf("%d%d",&x,&y);
printf("\nLCA=%d\n",lca(x,y));
return 0;
}
3.LCA求最短距离
/*
样例:
10 9
1 2 8
1 3 7
2 4 5
2 5 3
3 6 5
5 7 4
5 8 4
7 9 3
7 10 2
9 6
(求9,6的最短距离)
思路 求m到n最短距离
=m到lca的距离+n到lca的距离
=m到根的距离+n到根的距离-2*lca到根的距离
*/
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define M 1000
#define N 1000
using namespace std;
int n,m;int num=0;
int b[M],w[M],nt[M],p[N];
int fa[M][20],d[M],val[N];
void insert(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
num++;
b[num]=y;
w[num]=z;
nt[num]=p[x];
p[x]=num;
num++;
b[num]=x;
w[num]=z;
nt[num]=p[y];
p[y]=num;
}
}
void dfs(int x){
for(int i=1;i<=20;i++){//这里的20视题而定 这里表示最大深度为2^20
if(d[x]<(1<<i))break;
fa[x][i]=fa[fa[x][i-1]][i-1];//求fa数组的关键 和RMQ类似
}
for(int i=p[x];i>0;i=nt[i]){
int v=b[i];
if(v==fa[x][0]) continue;//无向边 不走已经走过的父节点
fa[v][0]=x;
d[v]=d[x]+1;//求深度=父节点深度+1
val[v]=val[x]+w[i];//求与根结点距离 和深度类似
dfs(v);
}
}
int lca(int x,int y){//求LCA
int h;
if(d[x]<d[y]) swap(x,y);
for(h=0;(1<<h)<=d[x];h++);
h--;
for(int i=h;i>=0;i--){
if(d[x]-(1<<i)>=d[y])
x=fa[x][i];
}
if(x==y) return x;
for(int i=h;i>=0;i--){
if(fa[x][i]!=fa[y][i]){
x=fa[x][i];
y=fa[y][i];
}
}
return fa[x][0];
}
int main(){
insert();
dfs(1);
int m,n;
scanf("%d%d",&m,&n);
printf("\nLCA=%d\n",lca(m,n));
printf("Distance=%d\n",val[m]+val[n]-2*val[lca(m,n)]);
return 0;
}