最近公共祖先 倍增算法
Posted V-sama
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最近公共祖先 倍增算法相关的知识,希望对你有一定的参考价值。
求最近公共祖先(Lowest Common Ancestor,LCA)
例题:洛谷P3379 【模板】最近公共祖先(LCA)
https://www.luogu.com.cn/problem/P3379
基本思路就是先用倍增把两点升到同一深度,然后倍增来找最近公共祖先。
其中fa数组是关键
#include<iostream>
#include<vector>
#define forup(i,l,r) for(int i=l;i<=r;i++)
#define fordown(i,l,r) for(int i=r;i>=l;i--)
using namespace std;
const int N =5e5+5;
int dep[N],fa[N][20];//fa[u][i]指从u向上跳2^i个结点到达的祖先结点
//2^20大约为一百万,具体为1048576
vector<int> child[N];//结点所连的结点(这里包括了父节点)
int read()
int n=0;
char m=getchar();
while(m<\'0\'||m>\'9\') m=getchar();
while(m>=\'0\'&&m<=\'9\')
n=(n<<1)+(n<<3)+(m^\'0\');
m=getchar();
return n;
void dfs(int father,int u)//作用是初始化dep和fa数组
dep[u]=dep[father]+1;
fa[u][0]=father;
forup(i,1,19)//跳2^i个相当于是先跳2^(i-1)到fa[u][i-1],然后再往上跳2^(i-1)到fa[u][i]
fa[u][i]=fa[fa[u][i-1]][i-1];
for(int v:child[u])//对子节点进行访问
if(v!=father) dfs(u,v);
int lca(int u,int v)
if(u==v) return u;//特判
if(dep[u]<dep[v]) swap(u,v);
fordown(i,0,19)
if(dep[fa[u][i]]>=dep[v])//dep的差=2^x1+2^x2+...+2^xn,故一定可以让u跳到和v同层的地方
u=fa[u][i];
if(u==v) return u;//特判二度
fordown(i,0,19)
if(fa[u][i]!=fa[v][i])//让u,v跳到最近公共祖先的下一个,原理同上一个for
u=fa[u][i]; v=fa[v][i];
return fa[u][0];
int main()
int n,m,root;
n=read(),m=read(),root=read();
forup(i,1,n-1)//建树
int u,v;
u=read(),v=read();
child[v].push_back(u);
child[u].push_back(v);
dfs(0,root);//作用是初始化dep和fa数组
forup(i,1,m)//查询
int u,v;
u=read(),v=read();
cout<<lca(u,v)<<endl;
return 0;
另外,当深度对应的结点数不那么明朗的时候比如不清楚n是2的多少次方,或者需要优化的时候,可以预处理一个lg数组来判断需要最多跳多少层,注意lg里面是深度或深度差
递推公式为lg[i]=lg[i-1]+((1<<(lg[i-1]+1))==i)
#include<iostream>
#include<vector>
#define forup(i,l,r) for(int i=l;i<=r;i++)
#define fordown(i,l,r) for(int i=r;i>=l;i--)
using namespace std;
const int N =5e5+5;
int dep[N],fa[N][20];//fa[u][i]指从u向上跳2^i个结点到达的祖先结点
//2^20大约为一百万,具体为1048576
int lg[N];//lg为log2(n)
vector<int> child[N];//结点所连的结点(这里包括了父节点)
int read()
int n=0;
char m=getchar();
while(m<\'0\'||m>\'9\') m=getchar();
while(m>=\'0\'&&m<=\'9\')
n=(n<<1)+(n<<3)+(m^\'0\');
m=getchar();
return n;
void dfs(int father,int u)//作用是初始化dep和fa数组
dep[u]=dep[father]+1;
fa[u][0]=father;
forup(i,1,lg[dep[u]])//跳2^i个相当于是先跳2^(i-1)到fa[u][i-1],然后再往上跳2^(i-1)到fa[u][i]
fa[u][i]=fa[fa[u][i-1]][i-1];
for(int v:child[u])//对子节点进行访问
if(v!=father) dfs(u,v);
int lca(int u,int v)
if(u==v) return u;//特判
if(dep[u]<dep[v]) swap(u,v);
while(dep[u]>dep[v])
u=fa[u][lg[dep[u]-dep[v]]];//2^lg始终小于等于dep的差
if(u==v) return u;//特判二度
fordown(i,0,lg[dep[u]])
if(fa[u][i]!=fa[v][i])//让u,v跳到最近公共祖先的下一个
u=fa[u][i]; v=fa[v][i];
return fa[u][0];
int main()
int n,m,root;
n=read(),m=read(),root=read();
forup(i,2,n)
lg[i]=lg[i-1]+((1<<(lg[i-1]+1))==i);//在此之前i都是处于[2^lg[i-1],2^lg[i])之间的数,即2^lg[i-1]*2>i
forup(i,1,n-1)
int u,v;
u=read(),v=read();
child[v].push_back(u);
child[u].push_back(v);
dfs(0,root);//作用是初始化dep和fa数组
forup(i,1,m)//查询
int u,v;
u=read(),v=read();
cout<<lca(u,v)<<endl;
return 0;
至于为什么不直接用c++自带的log函数,因为log比20快但是比lg数组慢
参考:https://www.cnblogs.com/dx123/p/16320461.html
https://blog.csdn.net/weixin_45697774/article/details/105289810
次方表:https://blog.csdn.net/weixin_44827418/article/details/106355287
图论-最近公共祖先-在线树上倍增
有关概念:
最近公共祖先(LCA,Lowest Common Ancestors):对于有根树T的两个结点u、v,最近公共祖先表示u和v的深度最大的共同祖先。
样例推导:
求17、13,16、6,1、15的LCA
指针1指向13,指针2指向17
i=0时,指针2移至fa[i][17],即12,使得深度与指针1相同
i=1时,fa[i][12]==fa[i][13],两指针同时移至5,LCA(17,13)=5
指针1指向6,指针2指向16
i=1时,指针2移至fa[i][16],即8,逼近指针1的深度
i=0时,指针2移至fa[i][8],即5,使得深度与指针1相同
i=0时,fa[i][5]==fa[i][6],两指针同时移至2,LCA(16,6)=2
指针1指向1,指针2指向15
i=2时,指针2移至fa[i][15],即1,使得深度与指针1相同
发现指针1和指针2重合,LCA(1,15)=1
不知大家发现没有,指针1开始一直指向深度较小的结点,这个在程序中有体现,主要是减小代码量
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 struct edge 5 { 6 int v,next,val; 7 }e[100005]; 8 int n,m,heads[50005],fa[17][50005],dis[50005],dep[50005],cnt; 9 void add(int u,int v,int val) 10 { 11 e[++cnt].next=heads[u]; 12 heads[u]=cnt; 13 e[cnt].v=v; 14 e[cnt].val=val; 15 } 16 void dfs(int u)//预处理dep和fa[0][j] 17 { 18 for(int i=heads[u];i;i=e[i].next) 19 { 20 if(e[i].v!=fa[0][u]) 21 { 22 dep[e[i].v]=dep[u]+1; 23 fa[0][e[i].v]=u; 24 dis[e[i].v]=dis[u]+e[i].val; 25 dfs(e[i].v); 26 } 27 } 28 } 29 int LCA(int u,int v) 30 { 31 if(dep[u]>dep[v])swap(u,v); 32 for(int i=16;~i;i--) 33 if(dep[fa[i][v]]>=dep[u]) 34 v=fa[i][v]; 35 if(u==v)return u; 36 for(int i=16;~i;i--) 37 if(fa[i][u]!=fa[i][v]) 38 { 39 u=fa[i][u]; 40 v=fa[i][v]; 41 } 42 return fa[0][u]; 43 } 44 int main() 45 { 46 scanf("%d",&n); 47 for(int i=1;i<n;i++) 48 { 49 int x,y,z; 50 scanf("%d%d%d",&x,&y,&z); 51 add(x,y,z); 52 add(y,x,z); 53 } 54 dep[1]=fa[0][1]=1; 55 dfs(1); 56 for(int i=1;i<=16;i++) 57 for(int j=1;j<=n;j++) 58 fa[i][j]=fa[i-1][fa[i-1][j]]; 59 scanf("%d",&m); 60 while(m--) 61 { 62 int x,y; 63 scanf("%d%d",&x,&y); 64 printf("%d\\n",dis[x]+dis[y]-2*dis[LCA(x,y)]);//两点之间路径长度 65 } 66 return 0; 67 }
以上是关于最近公共祖先 倍增算法的主要内容,如果未能解决你的问题,请参考以下文章