LCA最近公共祖先(倍增法)

本文介绍了在树结构中寻找两个节点最近公共祖先的问题,首先介绍朴素算法的O(n)和O(n^2)复杂度,然后引入倍增法(二分优化)将时间复杂度降低到O(nlogn),通过维护深度数组和fa数组实现高效查找。

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

               公共祖先:在一颗树上,若节点是c是a的祖先也是b的祖先就为a和b的公共祖先

               最近公共祖先:在a和b的所有公共祖先中深度最大的成为最近公共祖先,lca(a,b),least commom ancestor.

        首先,介绍一下朴素的算法,如果要找到a和b的最近公共祖先,我们令a的深度大于b的深度,然后从a开始一部一部往上走,知道a和b在同一高度,然后我们再继续让a和b同时往上走,直到俩个点为同一点。

然而这总算法的时间复杂度为n,如果有n组询问时间复杂度为n^2,大部分情况下肯定是会T的。所以就有了倍增法,也基于这种方式,做了一个二进制的优化。使时间复杂度为nlogn;

每一次我们向上跳都跳2的x次方,可以更加快速的达到,这里我们要维护俩个数组,fa[i][j],指的是从i点往上跳2^j次方所能达到的点,根据地推可以得到,fa[i][j]=fa[fa[i][j-1]][j-1];,还要维护一个depth数组,表示某节点的深度。具体细节代码如下

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<set>
#include<string>
#include<cstring>
#include<queue>
#include<cmath>
#include<map>
using namespace std;
typedef long long LL;
typedef double D;
const int N =5e6+10;
typedef pair<int,int>Pii;
int e[N],ne[N],h[N],idx;
int depth[N],fa[N][20];//深度和i节点往上跳2的j次方所能到达的点
void add(int a,int b){
  e[idx]=b;
  ne[idx]=h[a];
  h[a]=idx++;//链式前向星建图
}
void dfs(int u,int f){
  depth[u]=depth[f]+1;
  fa[u][0]=f;//记录父节点
  for(int i=1;(1<<i)<=depth[u];i++){//往上跳不能超过深度
    fa[u][i]=fa[fa[u][i-1]][i-1];//关键,u节点往上跳2^i-1到达的点再跳2^i-1
  }//通过递归
  for(int i=h[u];i!=-1;i=ne[i]){
    int v=e[i];
    if(v==f)continue;
    dfs(v,u);
  }
}
int lca(int a,int b){
  if(depth[a]<depth[b])swap(a,b);//让a处于更底层
  //让x和y跳到同一层,这里运用到了一个用2进制数拼凑的思想
  //类似于9 在二进制数中1,2,4,8,16
  //找到第一个比9小的数,9-8=1
  //继续找1-1=0;
 
  for(int i=19;i>=0;i--){
   
    if(depth[fa[a][i]]>=depth[b]){
        a=fa[a][i];//如果跳过去的深度依然比b深,就换一个数继续跳
      }
  }
  if(a==b)return a;
  //让a和b同步往上跳,找到lca
  ///注意:我们要跳到离他们最近公共祖先下面的一层,而不是跳到相等的那一层
  //这样处理会更方便
  for(int i=19;i>=0;i--){//如果跳出去了。那么这俩个都是0
    if(fa[a][i]!=fa[b][i]){//如果a和b没有跳到相等,那就继续往上跳
      a=fa[a][i];
      b=fa[b][i];//如果相等了就跳过头了,就不跳
    }
  }
  
  return fa[a][0];
}

void solved(){
  memset(h,-1,sizeof h);
  int n,m,root;
  cin>>n>>m>>root;
  for(int i=1;i<n;i++){
    int a,b;
    cin>>a>>b;
    add(a,b);
    add(b,a);
  }
  depth[root]=0;
  dfs(root,0);
  while(m--){
    int a,b;
    cin>>a>>b;
    cout<<lca(a,b)<<"\n";

  }
  
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int t;
    t=1;
    while(t--){
        solved();
    }
  return 0;
}

### 关于最近公共祖先LCA)问题的解法 #### 定义与背景 最近公共祖先(Lowest Common Ancestor, LCA),是指在一棵树中找到两个节点的最低共同父节点。这个问题在处理树形结构的数据时非常常见,在蓝桥杯竞赛以及其他编程比赛中也经常作为考察点之一。 #### 基础方法:暴力遍历 最简单的方法是从根节点开始向下逐层比较给定的两个目标节点的位置关系,直到遇到第一个能同时到达这两个节点的分支点为止。这种方法虽然直观易懂,但在大型或深层级数较多的情况下效率较低[^1]。 #### 改进方案:倍增算法 一种更高效的解决方案是采用倍增算法来求解LCA问题。此方法预先通过动态规划的方式记录下每个节点向上跳转\(2^i\)步后的祖先位置,从而可以在O(logN)时间内完成查询操作。具体步骤如下: - **预处理阶段**:对于每一个节点u及其高度h(u),计算并存储其所有可能的\(2^k\)-th父母节点parent[u][k]。 ```cpp void dfs(int u,int fa){ parent[u][0]=fa; depth[u]=depth[fa]+1; for (int i=1;(1<<i)<=depth[u];++i) parent[u][i]=parent[parent[u][i-1]][i-1]; // ...其他逻辑... } ``` - **查询阶段**:当需要寻找两节点u和v之间的LCA时,先调整两者至相同深度再逐步上移直至相遇。 ```cpp int lca_query(int u,int v){ if(depth[u]<depth[v]) swap(u,v); while(depth[u]>depth[v]){ int k=log2(depth[u]-depth[v]); u=parent[u][k]; } if(u==v)return u; for(int k=max_level;k>=0;--k){ if(parent[u][k]!=parent[v][k]){ u=parent[u][k]; v=parent[v][k]; } } return parent[u][0]; } ``` 这种基于倍增的思想不仅适用于普通的无权有向树,也可以扩展到加权边的情况,并且能够很好地满足比赛中的时间复杂度要求[^2]。 #### 应用于蓝桥杯竞赛 考虑到蓝桥杯对参赛者的基础知识掌握程度有一定要求,建议深入理解上述两种基本策略的基础上,多做练习题巩固知识点。特别是针对不同类型的输入规模优化自己的解答方式,提高程序运行速度和准确性。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值