最近公共祖先(LCA)之树上倍增法
来源:互联网 发布:suse linux 配置ip 编辑:程序博客网 时间:2024/05/16 04:41
最近公共祖先
对于有根树T的两个结点u,v,最近公共祖先LCA(T,u,v)表示一个节点x,满足x是u,v的祖先并且x的深度尽可能大。
也可以把T理解为一个无向无环图,而LCA(T,u,v)即u到v的最短路上森度最小的点
所以,LCA主要就是求解当两个点仅有唯一一条确定的最短路径时的路径信息的问题。
树上倍增
如果我们直接一个一个结点遍历去寻找这样的祖先,复杂度是O(n)的。而在算法竞赛中,经常会有个数较多的询问。比如50000个点,50000个询问,这时直接遍历的方法耗时较多。
下面是一种被称作树上倍增的算法,实际上它的思想类似于二分枚举法,时间复杂度是log(n)的。
终究都要走到一起(树根),可以考虑第一个不同祖先,这是倍增法的思想
所谓倍增,就是两倍,即从一个节点逐渐往上找k级父亲(所跨层数为
具体操作:先使两个结点深度相同(在同一层),然后将k从大到小进行枚举。之前要对
往往题目中不光要求LCA,还询问路径上的一些信息(如权值之和或者权值最大),因此还要另外记录。
参考代码
存储结构
const int maxn=100000;//节点数vector<Edge> edges;//边的具体信息vector<int> G[maxn];//边的编号const int maxlog=30;int grand[maxn][maxlog];int gdis[maxn][maxlog];int gmax[maxn][maxlog];int depth[maxn];int n;//结点数int s;//倍增最大步数int root;//根节点
预处理
void addEdge(int u,int v,int w){//vector邻接表 edges.push_back(Edge(u,v,w)); edges.push_back(Edge(v,u,w)); int size=edges.size(); G[u].push_back(size-2); G[v].push_back(size-1);}void dfs(int x)//预处理{ for(int i=1;i<=s;++i){//k级父亲 grand[x][i]=grand[grand[x][i-1]][i-1]; gdis[x][i]=gdis[x][i-1]+gdis[grand[x][i-1]][i-1]; gmax[x][i]=max(gmax[x][i-1],gmax[grand[x][i-1]][i-1]); if(!grand[x][i]) break; } for(int i=0;i<G[x].size();i++){ Edge & e=edges[G[x][i]]; if(e.to!=grand[x][0]){ depth[e.to]=depth[x]+1; grand[e.to][0]=x; gdis[e.to][0]=e.dist; gmax[e.to][0]=e.dist; dfs(e.to); } }}void init()//初始化{ s=floor(log(n+0.0)/log(2.0)); depth[0]=-1; memset(grand,0,sizeof(grand)); memset(gdis,0,sizeof(gdis)); memset(gmax,0,sizeof(gmax)); root=1; dfs(root);//以1为根结点建树}
核心代码
int lca(int a,int b,int &maxx,int &ans)//maxx,ans:最大值,路径权值和{ if(depth[a]>depth[b]) swap(a,b); ans=0;//路径权值和 maxx=gmax[b][0]; //for(int i=s;i>=0;i--) // if(depth[a]<depth[b]&&depth[grand[b][i]]>=depth[a]) // ans+=gdis[b][i],maxx=max(maxx,gmax[b][i]),b=grand[b][i]; int dre=depth[b]-depth[a]; for(int i=s;i>=0;--i){ if(dre&(1<<i)) ans+=gdis[b][i],maxx=max(maxx,gmax[b][i]),b=grand[b][i]; } if(a==b) return a; for(int i=s;i>=0;i--) if(grand[a][i]!=grand[b][i]){ ans+=gdis[a][i],ans+=gdis[b][i]; maxx=max(maxx,gmax[a][i]),maxx=max(maxx,gmax[b][i]); a=grand[a][i],b=grand[b][i]; } ans+=gdis[a][0]; ans+=gdis[b][0]; maxx=max(maxx,gmax[a][0]); maxx=max(maxx,gmax[b][0]); return grand[a][0];}
总体示例(vector)
#include<bits/stdc++.h>using namespace std;struct Edge{ int from,to,dist; Edge(int u,int v,int w):from(u),to(v),dist(w){ }};const int maxn=100000;//节点数vector<Edge> edges;//边的具体信息vector<int> G[maxn];//边的编号void addEdge(int u,int v,int w){//vector邻接表 edges.push_back(Edge(u,v,w)); edges.push_back(Edge(v,u,w)); int size=edges.size(); G[u].push_back(size-2); G[v].push_back(size-1);}const int maxlog=30;int grand[maxn][maxlog];int gdis[maxn][maxlog];int gmax[maxn][maxlog];int depth[maxn];int n;//结点数int s;//倍增最大步数int root;//根节点void dfs(int x)//预处理{ for(int i=1;i<=s;++i){ grand[x][i]=grand[grand[x][i-1]][i-1]; gdis[x][i]=gdis[x][i-1]+gdis[grand[x][i-1]][i-1]; gmax[x][i]=max(gmax[x][i-1],gmax[grand[x][i-1]][i-1]); if(!grand[x][i]) break; } for(int i=0;i<G[x].size();i++){ Edge & e=edges[G[x][i]]; if(e.to!=grand[x][0]){ depth[e.to]=depth[x]+1; grand[e.to][0]=x; gdis[e.to][0]=e.dist; gmax[e.to][0]=e.dist; dfs(e.to); } }}void init(){ s=floor(log(n+0.0)/log(2.0)); depth[0]=-1; memset(grand,0,sizeof(grand)); memset(gdis,0,sizeof(gdis)); memset(gmax,0,sizeof(gmax)); root=1; dfs(root);//以1为根结点建树}int lca(int a,int b,int &maxx,int &ans)//maxx,ans:最大值,路径权值和{ if(depth[a]>depth[b]) swap(a,b); ans=0;//路径权值和 maxx=gmax[b][0]; //for(int i=s;i>=0;i--) // if(depth[a]<depth[b]&&depth[grand[b][i]]>=depth[a]) // ans+=gdis[b][i],maxx=max(maxx,gmax[b][i]),b=grand[b][i]; int dre=depth[b]-depth[a]; for(int i=s;i>=0;--i){ if(dre&(1<<i)) ans+=gdis[b][i],maxx=max(maxx,gmax[b][i]),b=grand[b][i]; } if(a==b) return a; for(int i=s;i>=0;i--) if(grand[a][i]!=grand[b][i]){ ans+=gdis[a][i],ans+=gdis[b][i]; maxx=max(maxx,gmax[a][i]),maxx=max(maxx,gmax[b][i]); a=grand[a][i],b=grand[b][i]; } ans+=gdis[a][0]; ans+=gdis[b][0]; maxx=max(maxx,gmax[a][0]); maxx=max(maxx,gmax[b][0]); return grand[a][0];}int main(){ ios::sync_with_stdio(false); int t; cin>>t; while(t--) { int maxx,sum; edges.clear(); for(int i=0;i<maxn;++i){ G[i].clear(); } int query,u,v,w; cin>>n>>query; for(int i=1;i<=n-1;++i){ cin>>u>>v>>w; addEdge(u,v,w); } init(); for(int i=1;i<=query;++i){ cin>>u>>v; lca(u,v,maxx,sum); cout<<lca(u,v,maxx,sum)<<endl; cout<<maxx<<endl; cout<<sum<<endl; } } return 0;}
优化(链式向前星,数据量较大时可以优化常数)
//链式向前星也是存图的一种方式,用数组模拟邻接表,在点数较多时较vector优int head[2*maxn];void addEdge(int u,int v){//关键部分 edges[cnt].from=v; edges[cnt].to=head[u]; head[u]=cnt++;}
阅读全文
1 0
- 最近公共祖先(LCA)之树上倍增法
- 树上倍增求LCA(最近公共祖先)
- 最近公共祖先(LCA)---倍增法
- 树上两点最近公共祖先LCA的倍增算法 poj1986
- 倍增法求最近公共祖先 lca
- 最近公共祖先(LCA):倍增
- LCA最近公共祖先(朴素+倍增法)
- LCA(最近公共祖先)倍增法模板及总结
- LCA(最近公共祖先)倍增法实现
- 倍增法求最近公共祖先(LCA)
- 树上倍增方法求LCA(最近公共祖先)(转)
- LCA 在线倍增法 求最近公共祖先
- 【讲解+模板】最近公共祖先(LCA)(倍增)
- 最近公共祖先(LCA):tarjan与倍增
- lca(最近公共祖先)倍增模板【pascal】
- lca最近公共祖先(st表/倍增)
- 最近公共祖先(LCA)及其倍增算法实现
- 洛谷 3379 最近公共祖先(LCA 倍增)
- Redis配置文件参数说明
- 类的无参方法
- CSS3 box-sizing 属性
- 一、hibernate简单搭建小实例
- django入门 Templates介绍
- 最近公共祖先(LCA)之树上倍增法
- 数组的动态初始化
- 学习记录(4)
- 从零开始打jar包
- 一键分享
- Mybatis逆向工程
- hdu 3826 Hand in Hand 同构图★
- 见过最好的神经网络CNN解释
- 斐波切纳 数列 java