浅谈倍增查找LCA

来源:互联网 发布:sql 查询月份 编辑:程序博客网 时间:2024/06/06 09:09

    • 前言
    • LCA是什么
    • 如何实现LCA
    • LCA模板题
      • 题目描述 Description
      • 输入描述 Input Description
      • 输出描述 Output Description
      • 样例输入 Sample Input
      • 样例输出 Sample Output
      • 数据范围及提示 Data Size Hint

前言

这篇文章,是一篇总结倍增查找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 0