最近公共祖先LCA 模板
前置知识
LCA问题简述
最近公共祖先简称 LCA (Lowest Common Ancestor)。两个节点的最近公共祖先,就是这两个点的公共祖先里面,离根最远的那个。
为了方便,我们记某点集的最近公共祖先为 或。
自己是自己的祖先
性质
- 对于求多个点的最近公共祖先,我们并不真的要对所有点两两都求一次。我们只用取这些点中dfs序最小和最大的两个点来求最近公共祖先就行。
题目:https://www.cnblogs.com/kingwz/p/16518746.html
向上标记法
一般不用
- 从x向上走到根节点, 并标记路径上经过的点
- 从y向上走到根节点, 当遇到第一个被标记的点就找到了LCA(x, y)
倍增法
倍增的意思就是们不用每次向上爬一个,而是向上爬2^n个
具体分析看:原文
步骤: 每次向上爬2^n
- [1] 先将两个点跳到同一层
- [2] 让两个点同时往上跳,一直跳到它们的最近公共祖先的下一层。
数据定义:
- fa[i][j]表示从i开始,向上走2^j步所能走到的结点。0 <= j <= logn
- dep[i]表示深度
-
- 哨兵:如果从i开始跳2^j步会跳过根结点,那么fa[i][j] = 0。dep[0] = 0
预处理:
- 结点深度:
深度的更新就是他爸爸的深度+1 - 结点级的祖先
由于
所以:
i的级祖先的级祖先 就是i的2^j级祖先。
故:
fa[i][j]=fa[fa[i][j-1]][j-1]
复杂度:
- 预处理 O(nlogn)
- 查询 O(logn)
讲解代码
模板题洛谷p3379
#include <cmath> #include <cstdio> #include <vector> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int maxn=500005; vector <int> e[maxn]; int n,m,s,dep[maxn],fa[maxn][21]; int read() //快读 { int ans=0,flag=1; char ch=getchar(); while(!isdigit(ch)) { if(ch=='-') flag=-1; ch=getchar(); } while(isdigit(ch)) { ans=ans*10+ch-'0'; ch=getchar(); } return ans*flag; } void dfs(int x,int father) //x为当前节点,father为他的爸爸 { dep[x]=dep[father]+1; //x的深度是他父亲的深度+1 fa[x][0]=father; //2^0是1,x向上一个的祖先就是他爸爸 for(int i=1;(1<<i)<=dep[x];i++) //1<<i就是2^i,当2^i小于x的深度时,枚举他的祖先 { fa[x][i]=fa[fa[x][i-1]][i-1]; //上面说过的fa数组的更新 } for(int i=0;i<e[x].size();i++) //枚举与他相邻的边(我用的是vector存图) { if(e[x][i]!=father) //如果不是他爸爸 { dfs(e[x][i],x); //继续dfs } } return; } int lca(int u,int v) //u,v就相当于先讲的x和y { int temp; //temp是两个点的深度度差 if(dep[u]<dep[v]) //我们默认u的深度大一些,否则将u与v交换 swap(u,v); temp=dep[u]-dep[v]; //计算深度差 for(int i=0;(1<<i)<=temp;i++) //将u的深度变得与v相同,还是使用倍增的思想 { if((1<<i)&temp) //这个操作就相当于将深度差变成几个2^n的和,判断2^i是不是其中一个 { u=fa[u][i]; //将u跳到他祖先的位置 } } if(u==v) //如果u刚好等于v,即他们已经变成了同一个点 { return u; //返回这个点的值,就是LCA } for(int i=(int)(log(n)/log(2));i>=0;i--) //(int)(log(n)/log(2))就是n以内最大的2的次方,从最大的开始倍增 { if(fa[u][i]!=fa[v][i]) //如果他们的爸爸不相同,即没有找到LCA { u=fa[u][i]; v=fa[v][i]; //一起倍增 } } return fa[u][0]; //返回他们的爸爸,即是LCA } int main() { n=read(); m=read(); s=read(); for(int i=1;i<=n-1;i++) { int x=read(),y=read(); e[x].push_back(y); e[y].push_back(x); //vector存图 } dfs(s,0); //预处理 for(int i=1;i<=m;i++) { int x=read(),y=read(); int ans=lca(x,y); printf("%d\n",ans); } return 0; }
模板代码
模板题洛谷p3379
#include <cmath> #include <cstdio> #include <vector> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int maxn=500005; vector <int> g[maxn]; int n,m,s,dep[maxn],fa[maxn][21]; void dfs(int x,int f){ dep[x]=dep[f]+1; fa[x][0]=f; for(int i=1;(1<<i)<=dep[x];i++){ fa[x][i]=fa[fa[x][i-1]][i-1]; } for(int t:g[x]){ if(t!=f){ dfs(t,x); } } return ; } int lca(int u,int v){ if(dep[v]>dep[u]) swap(v,u); int temp=dep[u]-dep[v]; for(int i=0;(1<<i)<=temp;i++){ if((1<<i)&temp){ u=fa[u][i]; } } if(u==v) return u; int mx=log(n)/log(2); for(int i=mx;i>=0;i--){ if(fa[u][i]!=fa[v][i]){ u=fa[u][i],v=fa[v][i]; } } return fa[u][0]; } int main() { cin>>n>>m>>s; for(int i=1;i<=n-1;i++) { int x,y;cin>>x>>y; g[x].push_back(y); g[y].push_back(x); } dfs(s,0); for(int i=0;i<m;i++) { int x,y;cin>>x>>y; int ans=lca(x,y); printf("%d\n",ans); } return 0; }
Tarjan——离线求LCA O(n+m)
在深度优先遍历时,将所有点分成三大类:
- [1] 已经遍历过,且回溯过的点
- [2] 正在搜索的分支
- [3] 还未搜索到的点
模板代码
本文作者:kingwzun
本文链接:https://www.cnblogs.com/kingwz/p/16101581.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步