用“倍增法”求最近公共祖先(LCA)

本文深入解析了最近公共祖先(LCA)的概念及其两种实现方式:朴素算法和倍增法。通过实例和代码,详细展示了如何利用二进制思想优化查找过程,以达到O(logN)的时间复杂度。并提供了适用于树形结构查询问题的模板代码,旨在提升算法效率和解决实际应用场景中的路径问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.最近公共祖先:对于有根树T的两个结点u、v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u、的祖先且x的深度尽可能大。

2.朴素算法:记录下每个节点的父亲,使节点u,v一步一步地向上找父亲,直到找到相同的“祖先”,即是所求的答案,时间复杂度O(n)。

3.优化算法(倍增法):利用二进制的思想,想办法使一步一步向上搜变成以2^k地向上跳所以定义一个P[][]数组,使p[i][j]表示节点i的2^j倍祖先,因此p[i][0]即为i的父亲。我们可以得到一个递推式p[i][j]=p[p[i][j-1]][j-1]。这样子一个O(NlogN)的预处理(dfs)的 2^k 的祖先。定义一个deep[]数组表示节点深度,先判断是否 deep[u] > deep[v]果是的话就交换一下(保证 u的深度小于 v方便下面的操作)然后把u到与v同深度,同深度以后再把u v同时往上调(dec(j)) 调到有一个最小的j 满足: p[u] [j]!=p[v][j],u,v是在不断更新的   最后把u,v 往上调 (u=p[u,0] v=p [v,0]) 一个一个向上调直到   u= v 这时 u or v就是公共祖先。复杂度:O(logn)

 

下面给出 LCA 的模板:

输入:第一行:N,M,Q     (因为是一棵树,所以M==N-1)

        接下来M 行: u, v, c ,表示u到v连一条权值为c的边

        接下来Q行:u, v 表示寻求u,v的最近公共祖先,u~v的距离,u~v之间的路径的最大权值

输出:共Q行,对应上述的询问

 

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstdlib>
 4 #include<cmath>
 5 #include<algorithm>
 6 #include<cstring>
 7 #include<vector>
 8 #include<queue>
 9 using namespace std;
10 const int maxn=5000;
11 inline int read(){
12     int x=0,f=1;char ch=getchar();
13     while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
14     while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
15     return x*f;
16 }
17 int N,M,Q;
18 vector<int> to[maxn],cost[maxn];
19 int p[maxn][50],MAX[maxn][50],sum[maxn][50];
20 int dep[maxn];
21 inline void dfs(int root){
22     for(int i=0;i<to[root].size();i++){
23         int y=to[root][i];
24         if(y!=p[root][0]){
25             dep[y]=dep[root]+1;
26             p[y][0]=root;
27             MAX[y][0]=cost[root][i];
28             sum[y][0]=cost[root][i];
29             for(int k=1;k<=30;k++){
30                 int zu=1<<k;
31                 if(zu<=dep[y]){
32                     p[y][k]=p[p[y][k-1]][k-1];
33                     MAX[y][k]=max(MAX[y][k-1],MAX[p[y][k-1]][k-1]);
34                     sum[y][k]=sum[y][k-1]+sum[p[y][k-1]][k-1];
35                 }
36             }
37             dfs(y);
38         }
39     }
40 }
41 inline void LCA(int x,int y){
42     int ans1=0,ans2=0;
43     if(dep[x]>dep[y]) swap(x,y);
44     int delta=dep[y]-dep[x];
45     for(int i=0;i<=30;i++){
46         int h=1<<i; h=h&delta;
47         if(h!=0){
48             ans1+=sum[y][i]; ans2=max(ans2,MAX[y][i]);
49             y=p[y][i];
50         }
51     }
52     if(x==y){
53         cout<<x<<" "<<ans1<<" "<<ans2<<endl;
54         return ;
55     }
56     for(int i=30;i>=0;i--){
57         if(p[y][i]!=p[x][i]){
58             ans1+=sum[x][i]; ans1+=sum[y][i];
59             ans2=max(ans2,MAX[x][i]); ans2=max(ans2,MAX[y][i]);
60             x=p[x][i]; y=p[y][i];
61         }
62     }
63     ans1+=sum[x][0]; ans1+=sum[y][0];
64     ans2=max(ans2,MAX[x][0]); ans2=max(ans2,MAX[y][0]);
65     cout<<p[x][0]<<" "<<ans1<<" "<<ans2<<endl; 
66 }
67 int main(){
68     N=read(); M=read(); Q=read();
69     for(int i=1;i<=M;i++){
70         int u,v,c;
71         u=read(); v=read(); c=read();
72         to[u].push_back(v); to[v].push_back(u);
73         cost[u].push_back(c); cost[v].push_back(c);
74     }
75     p[1][0]=-1; dep[1]=0;
76     dfs(1);
77     for(int i=1;i<=Q;i++){
78         int u,v;
79         u=read(); v=read();
80         LCA(u,v);
81     }
82     return 0;
83 }

 

转载于:https://www.cnblogs.com/CXCXCXC/p/4626591.html

### 使用倍增法最近公共祖先 (LCA) 的算法实现与解释 #### 算法概述 倍增法是一种高效的动态规划技术,适用于处理静态树结构中的查询问题。该方法通过预先计算并存储每个节点的第 \(2^i\)祖先来加速后续查询过程。 #### 数据预处理阶段 为了能够快速定位任意两点间的最近公共祖先,在初始化时需构建辅助数组 `f` 和记录各顶点深度的数组 `depth` 。其中 `f[u][j]` 表示从节点 u 出发经过 \(2^j\) 步所能到达的父亲节点位置;当 j=0 时表示直接父亲节点的位置。 ```cpp void dfs(int node, int parent){ depth[node]=depth[parent]+1; f[node][0]=parent; // 初始化每一点的第一层祖先为自己真正的父节点 for(int i = 1 ;(1<<i)<=depth[node];++i) f[node][i]=f[f[node][i-1]][i-1]; for(auto child : adj[node]) if(child!=parent) dfs(child,node); } ``` 上述代码片段实现了自底向上的遍历操作,并完成了对 `f[][]` 数组以及 `depth[]` 数组的填充工作[^1]。 #### 查询函数设计 在完成数据准备之后就可以编写具体的询问逻辑了: 1. **调整两结点至相同高度** 如果两个待查节点不在同一层次,则先让较深的那个往上跳若干步直至两者处于平行状态为止; 2. **同步上升寻找共同祖先** 接着利用之前建立起来的信息表不断尝试使二者同时沿路径返回根部方向移动,直到它们首次交汇于某处即为目标答案所在之处。 3. **特殊情况判断** 若其中一个目标本身就是另一个的目标之一则无需执行第二步流程可直接给出结论。 下面是完整的 C++ 版本实现方式: ```cpp int lca_query(int a,int b){ if(depth[a]<depth[b]) swap(a,b); // 让a成为更深的一个点 for(int k=logn;k>=0;--k){ // 将a提升到b的高度 if((depth[a]-depth[b])&(1<<k)) a=f[a][k]; } if(a==b)return a; for(int k=logn;k>=0;--k){ if(f[a][k]!=f[b][k]){ a=f[a][k]; b=f[b][k]; } } return f[a][0]; } ``` 此段程序展示了如何基于前期准备工作高效地获取指定节点之间的最低公共祖先关系[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值