填坑行动8-最近公共祖先LCA(树上倍增) 学习笔记
板子题
题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入格式
第一行包含三个正整数分别表示树的结点个数、询问的个数和树根结点的序号。
接下来行每行包含两个正整数表示结点和结点之间有一条直接连接的边(数据保证可以构成树)。
接下来行每行包含两个正整数,表示询问结点和结点的最近公共祖先。
输出格式
输出包含行,每行包含一个正整数,依次为每一个询问的结果。
输入输出样例
输入 #1
5 5 4 3 1 2 4 5 1 1 4 2 4 3 2 3 5 1 2 4 5
输出 #1复制
4 4 1 4 4
说明/提示
对于的数据,。
对于的数据,。
对于的数据,。
样例说明:
该树结构如下:
第一次询问:的最近公共祖先,故为。
第二次询问:的最近公共祖先,故为。
第三次询问:的最近公共祖先,故为。
第四次询问:的最近公共祖先,故为。
第五次询问:的最近公共祖先,故为。
故输出依次为。
算法解析
首先大家都可以想到,这道题目可以使用来解决这个问题,但是,我们很容易得出这个算法并不能A掉这道题目,的算法复杂度是,超时。
我们来分析一下的步骤:
- 将较低的节点提升,使两个节点在同一高度。
- 将两个节点提升,直到重合。
我们发现在上述过程中,都需要将两个节点向上提,最坏情况下提次,那么怎样才能吧节点提起更快呢?也就是一次不仅仅提一个。
倍增就可以做到。我们学过RMQ问题的同学都知道,RMQ使在一条链上进行倍增,而倍增版本的LCA也是相同的原理。令为节点向上提个节点是多少,不难得出递推公式:
写在代码中就是:f[i][j]=f[ f[i][j-1 ][j-1]
解读:相当于节点向上提次就相当于先提次,在提次即可。
预处理代码如下:
void dfs(int num,int pre){ f[num][0]=pre; deep[num]=deep[pre]+1; for(int i=1;i<=t;i++) f[num][i]=f[f[num][i-1]][i-1]; for(int i=head[num];i;i=nex[i]) if(to[i]!=pre) dfs(to[i],num); }
得到了,我们就可以得出了,具体做法和dfs一样,但是需要注意两点:
- 在提节点的时候要注意从大到小计算。
- 当两个节点位于同一高度时,要特判两个点是否重合。
- 注意在向上提两个点的时候,需要让两个点不相等,不然会提过头。
然后就是LCA的代码了:
int lca(int x,int y){ if(deep[x]<deep[y]){ x^=y; y^=x; x^=y; }//交换 if(deep[x]>deep[y]){ for(i=t;i>=0;i--) if(deep[f[x][i]]>=deep[y]&&f[x][i]!=0) x=f[x][i]; //if(deep[x]!=deep[y]) printf("*"); //翻车标记 } if(x==y) return x; for(i=t;i>=0;i--) if(f[x][i]!=f[y][i]&&f[x][i]!=0&&f[y][i]!=0) x=f[x][i],y=f[y][i]; //if(f[x][0]!=f[y][0]) printf("*"); //翻车标记 return f[x][0]; }
最后是完整的AC代码:
#include<cstdio> #include<cmath> #define maxn 500039 using namespace std; int head[maxn],nex[maxn<<1],to[maxn<<1],k; #define add(x,y) nex[++k]=head[x];\ head[x]=k;\ to[k]=y; int u,v,f[maxn][20],deep[maxn]; int root,n,m,i,j,T,t; void dfs(int num,int pre){ f[num][0]=pre; deep[num]=deep[pre]+1; for(int i=1;i<=t;i++) f[num][i]=f[f[num][i-1]][i-1]; for(int i=head[num];i;i=nex[i]) if(to[i]!=pre) dfs(to[i],num); } int lca(int x,int y){ if(deep[x]<deep[y]){ x^=y; y^=x; x^=y; }//交换 if(deep[x]>deep[y]){ for(i=t;i>=0;i--) if(deep[f[x][i]]>=deep[y]&&f[x][i]!=0) x=f[x][i]; //if(deep[x]!=deep[y]) printf("*"); //翻车标记 } if(x==y) return x; for(i=t;i>=0;i--) if(f[x][i]!=f[y][i]&&f[x][i]!=0&&f[y][i]!=0) x=f[x][i],y=f[y][i]; //if(f[x][0]!=f[y][0]) printf("*"); //翻车标记 return f[x][0]; } int main(){ scanf("%d%d%d",&n,&T,&root); for(i=1;i<n;i++){ scanf("%d%d",&u,&v); add(u,v); add(v,u); } deep[0]=-1; t=(int)log2(n)+1; dfs(root,0); int x=0,y=1; while(T--){ scanf("%d%d",&u,&v); printf("%d\n",lca(u,v)); } return 0; }
都0202年了应该没人用邻接矩阵的吧。
关于LCA的拓展
LCA不仅仅能处理最近公共祖先,而且还可以处理两个点到最短公共祖先的距离、最短路、最大最小点权、最大最小边……这里就不一一叙述了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具