浅谈倍增查找LCA

前言

这篇文章,是一篇总结倍增查找LCA的一篇文章,同时也是实现在浅谈RMQ算法文章中承诺.若有疑问或建议,请于下方留言,谢谢!

LCA是什么?

LCA(Least Common Ancestors),即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先

如何实现LCA?

  1. 暴力直接找
    1. 首先将u,v中深度较深的那个点蹦到和较浅的点同样的深度
    2. 然后两个点一起向上蹦直到蹦到同一个点,这个点就是它们的LCA
    3. 时间复杂度在极端条件下可以到O(N)
  2. 运用DFS序
    1. DFS序就是用DFS方法遍历整棵树得到的序列。
    2. 两个点的LCA一定是两个点在DFS序中出现的位置之间深度最小的那个点
    3. 寻找最小值可以用使用我们前篇文章讲的RMQ
  3. 运用倍增思想
    1. 我们令father[i][j]表示编号为i的点,往上蹦2^j次的父亲是谁,其实这个预处理和RMQ很相似,先从大到小枚举j,然后令father[i][j]=father[father[i][j-1]][j-1],这样预处理的时间复杂度为O(NlogN)
    2. 接下来是查询,可以分两步走
      1. 将u和v移动到同样的深度
      2. u和v同时向上移动,直到重合。第一个重合的点即为LCA
    3. 再是移动到同样的深度
      1. 令u为深度较大的点。我们从 log2n  , 到0枚举,令枚举的数字为j。如果从u向上跳 2j 步小于了v的深度,不动;否则向上跳 2j 步。这样一定能移动到和v一样的深度。
      2. 假设一共要跳k步,上面的算法相当于枚举k的每个2进制为是0还是1
    4. 从同样的深度移动到同一个点
      1. 和上一步类似。从 log2n  到0枚举,令枚举的数字为j。如果两个向上跳 2j 步将要重合,不动,否则向上跳 2j 步。
      2. 通过这种办法u和v一定能够到达这样一种状态——它们当前不重合,如果再向上一步就重合。所以再上一步就得到了LCA
    5. 由于本质上是枚举每一个二进制位,所以单次查询的复杂度为 log2n 

我们可以注意到,在整个倍增查找LCA的过程中,从u到v的整条路径都被扫描了一遍。如果我们在倍增数组F[i][j]中再记录一些别的信息,就可以实现树路径信息的维护和查询

LCA模板题

这里给一个LCA用倍增思想的模板,题目来源codevs4605 (传送门

题目描述 Description

顾名思义.给一棵有根树,以及一些询问,每次询问树上的2 个节点A、B,求它们的最近公共祖先.

输入描述 Input Description

第一行一个整数N.

接下来N 个数,第i 个数Fi 表示i 的父亲是Fi. 若Fi = 0,则i 为树根.

接下来一个整数M.

接下来M 行,每行2 个整数A、B,询问节点(A xor LastAns)、(Bxor LastAns)的最近公共祖先. 其中LastAns 为上一个询问的答案,一开始LastAns = 0.

输出描述 Output Description

对每一个询问输出相应的答案.

样例输入 Sample Input

10
0 1 2 3 2 4 2 5 4 9
10
3 9
2 7
7 8
1 1
0 6
6 11
6 3
10 7
2 15
7 7

样例输出 Sample Output

3
1
4
5
2
4
2
5
2
5

数据范围及提示 Data Size & Hint

30% n,m≤1000
100% n,m≤100,000
代码
不解释了吧,上代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
int n,m;
vector <int> a[100001];
int f[100001][18];
int father[100001];
int root;
int height[100001];
int lca(int x,int y)
{
        if(height[x]<height[y])
        {
           int tt=x;
           x=y;
           y=tt;                       
        } 
        int t=0;
        while((1<<t) <= height[x])t++;
        t--;
        int i;
        for(i=t;i>=0;i--)
        {
           if(height[x]-(1<<i)>=height[y])
             {
                 x=f[x][i];                             
             }                 
        }
        if(x==y)return x;
        for(i=t;i>=0;i--)
        {
           if(f[x][i]!=f[y][i])
           {
              x=f[x][i];
              y=f[y][i];                    
           }                 
        }
      return f[x][0];  
}
void dfs(int x,int deep)
{
     int i,j;
     height[x]=deep;
     for(i=1;i<=17;i++)
     {
         f[x][i]=f[f[x][i-1]][i-1];                  
     }   
     for(i=0;i<a[x].size();i++)
     {
         dfs(a[x][i],deep+1);                          
     }
}
int main()
{
   scanf("%d",&n);
   int i,j;
   for(i=1;i<=n;i++)
   {
     scanf("%d",&father[i]);  
     f[i][0]=father[i];
     if(f[i][0]==0)
     {
        root=i;              
     }
     a[father[i]].push_back(i);
   }
   dfs(root,0);
   scanf("%d",&m);
   int ans=0;
   int x,y;
   for(i=1;i<=m;i++)
   {
       scanf("%d%d",&x,&y);                
       x=x^ans;y=y^ans;
       ans=lca(x,y);
       printf("%d\n",ans);
   }
   return 0;    
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值