• 【模板时间】◆模板·I◆ 倍增计算LCA


    【模板·I】LCA(倍增版)

    既然是一篇重点在于介绍、分析一个模板的Blog,作者将主要分析其原理,可能会比较无趣……(提供C++模板)

    另外,给reader们介绍另外一篇非常不错的Blog(我就是从那篇博客开始自学LCA的):+LCA-by 殇雪+


    一、原理

     LCA即最近公共祖先,一般用LCA(u,v)表示u、v的最近公共祖先。举个例子:

    LCA举例

    (Tab:以下“树”均指有根树)由于在树中,除根节点的每个节点都有且仅有一个父节点,我们很容易得到一个结论——u,v的最近公共祖先的任意祖先一定也是u,v的公共祖先。假设x是u,v的公共祖先,LCA(u,v)≤x。倍增求LCA的基础是 2进制能够表示任意整数 。所以令u,v的最近公共祖先与u、v分别距离lu、lv,并可以将其表示成2进制。

    设 dfu[v][k] 表示 节点v向上寻找到的第2k代祖先(比如之前的图中,dfu[4][1]=1),dep[v] 表示 v的深度(根节点的深度依照题目定为1或0)。

    不妨设 dep[v]>dep[u] 。首先要使u、v同层,即dep[u]=dep[v],可以通过将v上移到它的 dep[u]-dep[v] 代祖先来实现,再将dep[v]-dep[u]转为2进制,就可以通过dfu实现。

    u、v同层后,设它们距离最近公共祖先l个单位。同样,l也可以表示为2进制,也就可以通过dfu解决。

    由于每一次移动都是在2进制下进行,求LCA的复杂度大约可看为 O(log2n)

    二、算法实现

    ①大致步骤:

    初始化:  DFS初始化每个点v的dep[v]以及直接父亲(dfu[v][0]);

             递推计算全部dfu;

    计算LCA: 上移u、v至同一层;

     同时上移u、v找最近公共祖先;

    ②初始化:

    DFS可以通过参数下传父亲节点以及节点深度(eg:void DFS(int u,int fa,int depth))。(建议用邻接表的方式)遍历每一个儿子,同时初始化。

    根据dfu定义,dfu[v][i+1]表示v的第2i+1代祖先,也就是第(2i+2i)代祖先。则可以先找到v的第2i代祖先u,再找到u的第2i代祖先。递推式如下:

    dfu[v][i+1]=dfu[dfu[v][i]][i]; //dfu[v][i]表示v的第2i代祖先

    由于递推过程中,dfu[v][i]已经计算出来了,我们就可以从dfu[v][0]出发推出所有dfu。

    ③计算LCA(u,v)

    为了方便计算,先保证dep[v]>dep[u]。

    要将u、v移动到同层,即把v上移(dep[u]-dep[v])层。由于我们知道v的2k代祖先,我们可以把dep[u]-dep[v]拆分成 2a1+2a2+...+2ap(C++实现可以判断 (dep[v]-dep[u])>>i&1,即2进制的(dep[v]-dep[u])的第i位是否为1),按次上移即可。

    如果此时u=v,则说明最初u是v的祖先,则LCA(u,v)=u。

    除开上述u=v的情况。由于 LCA(u,v) 的祖先一定也是u,v的公共祖先,所以我们可以将u、v上移到LCA(u,v)的下一层,即u,v的父亲为LCA(u,v)。从高到低枚举i(i最大为ceil(log2(n)),即退化为链后根与叶子节点),如果dfu[u][i]==dfu[v][i],则说明dfu[u][i]已经是LCA(u,v)或层数已经高于LCA(u,v)了,由于无法直接判断是否是LCA(u,v),我们就可以选择不上移(目标是将u,v转移到LCA(u,v)的下一层);如果dfu[u][i]!=dfu[v][i],则说明还没有到LCA(u,v),就可以上移。最后就可以移动到LCA(u,v)的下一层。

    其实上述操作无非是令u,v到LCA(u,v)的距离为S,将S-1表示为2进制,再通过dfu顺次上移。最后得到dfu[u][0](或者dfu[v][0])就是LCA(u,v)了。

    三、C++代码

    初始化:

     1 void DFS(int u,int fa,int depth)
     2 {
     3     dfu[u][0]=fa;dep[u]=depth; //更新v的父节点以及深度
     4     for(int i=0;i<lnk[u].size();i++) //lnk是vector的邻接表
     5         DFS(lnk[u][i],u,depth+1);
     6 }
     7 void Prepare()
     8 {
     9     DFS(0,-1,1); //先处理出节点的深度(dep)和直接祖先(dfu[0])
    10     for(int i=0;i+1<20;i++)
    11         for(int j=0;j<n;j++)
    12             if(dfu[j][i]<0) dfu[j][i+1]=-1; //上移位置已经超过根节点
    13             else dfu[j][i+1]=dfu[dfu[j][i]][i];
    14 }
    View Code

    计算LCA:

     1 int LCA(int u,int v)
     2 {
     3     if(dep[u]>dep[v]) swap(u,v); //保证u不高于v
     4     for(int i=0;i<20;i++) //拆分二进制
     5         if(((dep[v]-dep[u])>>i)&1) //上移到同一层
     6             v=dfu[v][i];
     7     if(u==v) return u; //u最初是v的根节点
     8     for(int i=19;i>=0;i--)
     9         if(dfu[u][i]!=dfu[v][i])
    10             u=dfu[u][i],v=dfu[v][i];
    11     return dfu[u][0];
    12 }
    View Code

    The End

    Thanks for reading!

    - Lucky_Glass

    (Tab:如果我有没讲清楚的地方可以直接在邮箱lucky_glass@foxmail.com email我,在周末我会尽量解答并完善博客~)

  • 相关阅读:
    python类内置方法之__call__
    selenium之python源码解读-webdriver继承关系
    Jmeter之JDBC类型组件
    Jmeter逻辑控制之if控制器
    Java连接MySQL Warning: Establishing SSL connection without server's identity verification is not recommended
    Python3 Windows服务器简单实现 手机访问
    如何在C语言 C++里面调用 DOS命令
    常用DOS命令(1) color,dir,copy,shutdown,mkdir(md),rmdir(rd),attrib,cd
    Java实现队列
    Java 实现 栈
  • 原文地址:https://www.cnblogs.com/LuckyGlass-blog/p/9325629.html
Copyright © 2020-2023  润新知