最近公共祖先(简称LCA(Lowest Common Ancestor)):官方定义是对于有根树T的两个结点u、v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u、v的祖先且x的深度尽可能大。
其实就是一棵树上两个点,这两点公共的离这两个点最近的公共节点。就好比下图:
在这个图中,千万别把根节点搞错! 乍一看,好像是8,那就等着wa吧。仔细看一下,图其实根节点是9。
例如,6,12的公共节点为4,8,5,9,最近公共祖先为4。
这里讲一下如何使用倍增法求最小公共祖先。
核心思路:
- init()初始化的构建,需要多次用到2的i次方,因此需要建立一个bit[]数组存储一下2的i次方。i一般小小于30.
- father[][]数据的构建。所谓倍增,就是以2的次方进行寻找某个节点的祖先节点。在图中,12的2^0节点为16,2^1为10,2^2为4,这样以2的次方进行查找,这个过程是利用dfs实现,把每个点的第2的i次方个节点存放在数组里。father[i][j]=i节点的第2^j节点。
- LAC函数的构建。做完了步骤一的工作,就进入寻找最近公共祖先。首先判断a,b两个节点的深度差(深度就是距离根节点之间有几个节点,包括自己本身),再将二者提升到相近深度,这里也是利用倍增的方法。
核心代码:
const int N=1e6+10;
long long bit[N];//用于存储2次方
int depth[N];//存储节点的深度
int fa[N][30];//fa[i][j]用于存储i上2的j次方的点
vector<int>e[N];
int n,m,s;
void init(){//求出2的各个次方
bit[0]=1;
for(int i=1;i<=29;i++)
bit[i]=(bit[i-1])*2;
}
void dfs(int u,int par){
depth[u]=depth[par]+1;
fa[u][0]=par;//u点上的2零次方节点为par
for(int i=1;i<=29;i++)
fa[u][i]=fa[fa[u][i-1]][i-1];
//u的第2的i次方个节点等于u的i-1次方节点的第2的i-1个次方节点
for(int i=0;i<e[u].size();i++){//遍历与u相连的每一个点
int v=e[u][i];
if(v==par)
continue;
dfs(v,u);
}
}
int lca(int a,int b){
if(depth[a]<depth[b])
swap(a,b);//保证a的深度最大
int dif=depth[a]-depth[b];//计算二者深度差
for(int i=29;i>=0;i--){
if(dif>=bit[i]){//判断深度差是否大于2的i次方,以防止跳过
a=fa[a][i];//a往上调,深度减小
dif-=bit[i];
}
}
if(a==b)return a;
for(int i=29;i>=0;i--){
if(depth[a]>=bit[i]&&fa[a][i]!=fa[b][i])//深度足够并且a,b的第i个节点不同
{ a=fa[a][i];
b=fa[b][i];
}
}
return fa[a][0];
}
经典模板题: Nearest Common Ancestors(POJ1330)
题目大意:有t组测试数据,每组测试数据有n个点,前n-1行给出相连接的节点。(树可参照上图),第n行给出两个节点,求其最近公共祖先。
题解:根据最先公共祖先的套路,得先把所有的节点及其第2的i次方个节点放入一个数组,故此得先找出根节点。根节点的入度为0,在数据输入的时候需要统计一下每个点的入度。然后利用dfs将father[][]数组填充完整,进入LCA函数,最后输入就可。输入n-1个节点时,利用vector存图比较方便。
今天写这道题,因为主函数部分WA了好几次。首先是输入时,n-1组数据构建图,最后一对数是用来求最近公用祖先的;其次是对根节点的判断,要利用入度判断一下;最后,数组使用后要记得初始化,vector的初始化只能一个一个来,利用for循环,e[i].clear();.
AC代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cmath>
using namespace std;
const int N=1e4+10;
int f[N][30];
int depth[N];
long long bit[N];
int n;
int in[N];
int node;
int x,y;
vector<int>e[N];
void init(){
bit[0]=1;
for(int i=1;i<=29;i++)
bit[i]=bit[i-1]*2;
}
void dfs(int u,int par){
depth[u]=depth[par]+1;
f[u][0]=par;
for(int i=1;i<=29;i++){
f[u][i]=f[f[u][i-1]][i-1];
}
for(int i=0;i<e[u].size();i++){
int v=e[u][i];
if(v==par)
continue;
dfs(v,u);
}
}
int lca(int a,int b){
if(depth[a]<depth[b])
swap(a,b);
int dif=depth[a]-depth[b];
for(int i=29;i>=0;i--){
if(dif>=bit[i])
{
a=f[a][i];
dif-=bit[i];
}
}
if(a==b) return a;
for(int i=29;i>=0;i--){
if(depth[a]>=bit[i]&&f[a][i]!=f[b][i]){
a=f[a][i];
b=f[b][i];
}
}
return f[a][0];
}
int main(){
int t,m;
cin>>t;
init();
while(t--){
memset(f,0,sizeof f);
memset(depth,0,sizeof depth);
memset(in,0,sizeof in);
cin>>n;
m=n-1;
while(m--){
cin>>x>>y;
e[x].push_back(y);
e[y].push_back(x);
in[y]++;
}
for(int i=1;i<=n;i++){
if(in[i]==0)
{
node=i;
break;}
}
dfs(node,0);
cin>>x>>y;
printf("%d\n",lca(x,y));
for(int i=1;i<=n;i++)
e[i].clear();
}
return 0;
}
感觉今天学习的主题可以叫做找爸爸的爸爸hhhh~
博主还是蒟蒻,以上思路若有错误,请大佬们给指出!