• LCA 最近公共祖先


    LCA(不知道全称叫什么)

    -倍增版-

    作用:在一棵树中求两点公共祖先中深度最大的那个

      存在一颗 n 点的树,询问 m 次两个点的最近公共祖先

    复杂度:预处理O(nlogn),询问O(logn)

    主要思想:倍增

    预处理:

      预处理出 f [ i ] [ j ] 数组,表示 i 点向上 2j 步的点,即自己往上数第 2j 个祖先 

      预处理出 dep [ i ] 数组,表示以第 i 个点的深度 

       

      这样先给出一张以1为根的样例树,比较好解释一点,这里的值都是相对于根为1的情况,预处理的全部内容以该树为参考

      那么 f [ 12 ] [ 0 ] = 8 , f [ 12 ] [ 1 ] = 5 , f [ 12 ] [ 2 ] = 1

      那么 dep [ 1 ] = 1 , dep [ 5 ] = 3 , dep[ 13 ] = 5

      i 点向上 2j 相当于 i 点往上数的第 2j-1 个祖先向上 2j-1 步 

      预处理转移方程:

    for(int i=1;(1<<i)+1<=dep[u];++i)
            f[u][i]=f[f[u][i-1]][i-1];

      dep [ ] 数组的处理用dfs完成,于是在处理dep [ ] 的同时,可以顺便处理 f [ ] [ ]数组

      那么预处理只需要一个dfs即可完成。

      注意很多时候给边的时候未给定边的方向(比如只说u点到v点有一条边,没说明是u到v或v到u),建边要双向,由一个根开始的dfs来确定每条边的方向。

    void dfs(int u,int fa){
        v[u]=true;
        f[u][0]=fa;
        dep[u]=dep[fa]+1;
        for(int i=1;(1<<i)+1<=dep[u];++i)
            f[u][i]=f[f[u][i-1]][i-1];
        for(int i=head[u];i;i=edge[i].nxt)
            if(!v[edge[i].to])
                dfs(edge[i].to,u);
    }
    
    int main(){
        dfs(1,0);      
    }

      注意dfs中 fa 是 u 的父亲。

      为什么dfs的开始可以 0 是 1 的父亲开始呢?

      这样预处理难道不会出现问题吗?

      还真没有,将 0 定为 1 的父亲后,由于第一个for中 (1<<i)+1<=dep[u] 所以除了 1 的点全部不会将 0 作为父亲

      即使全把 0 做父亲也没有事情,因为最近公共祖先的定义保证了两个点最大的公共最先顶多是根,不会到 0

    查询:

      在讲查询之前先来说说倍增的思想

      一个 x 个单位长的区间,可以分解为几个不同的 2k 的长度的小区间

      如14可以拆成8+4+2

      

     

      同样的,我们想到从0到14可以拆解为如上三个步骤,先走 23 步再走 22 步再走 21 

      相关知识可以证明这个步数的组成是不同的且唯一的 (请查阅小学奥数)   

      如此一来在一棵树中,求一个点往上走 q 步到达的祖先只需要 logq 的复杂度

      我们考虑 x,y两点处在共同的深度时

      他们有许多的公共祖先,若 f [ x ] [ k ]与f [ y ] [ k ] 相同,代表他们向上走 2k 步的点是他们的公共祖先

      

      如图 a 是x、y的最近公共祖先,a的祖先都是x、y的祖先,p、q是 a 儿子且dep[p]=dep[q]=dep[a]+1

      LCA的大致思想是不断更新x,y的位置,让他们向上爬,p是我们希望x最终到达的位置,q是我们希望y最终到达的位置。

      我们每次确定一个值k,表示让x,y都爬 2k 步,之前说了x到p与y到q的路径可以拆分成不同的2k,所以让k改变,判断这次的k是否需要爬即可

      可以发现如果走到a或a的祖先(x、y的公共祖先)时,x和y的位置变为相同,需要爬的部分在于a以下

    int same_LCA(int x,int y){
        int k=log2(dep[x])+1;
        while(k>=0){
            if(f[x][k]!=f[y][k]) //本次爬完完在x~p和y~q的范围内
                x=f[x][k],y=f[y][k];  //爬
            --k;
        }      
       //此时的x,y即为p,q的位置 }

      那么此时的fa[x][0]= p往上爬一步= a号点 =最近公共祖先

      

      我们再考虑 x,y 不同深度

      那么简单,把x,y调成同深度即可,原理与上面相同

    if(dep[x]<dep[y]) swap(x,y);
        while(dep[x]>dep[y]){
            int l=log2(dep[x]-dep[y]);
            x=f[x][l];
        }

      

    然后再贴个代码,模板题是[洛谷 P3379 【模板】最近公共祖先(LCA)]

    #include<bits/stdc++.h>
    #define maxn 1000010
    using namespace std;
    
    int n,m,s,edge_num;
    int head[maxn],f[maxn][21],father[maxn],dep[maxn];
    bool v[maxn];
    struct {
        int nxt,to;    
    }edge[maxn];
    
    void add_edge(int from,int to){
        edge[++edge_num].nxt=head[from];
        edge[edge_num].to=to;
        head[from]=edge_num;
    }
    
    void dfs(int u,int fa){
        v[u]=true;
        f[u][0]=fa;
        dep[u]=dep[fa]+1;
        for(int i=1;(1<<i)+1<=dep[u];++i)
            f[u][i]=f[f[u][i-1]][i-1];
        for(int i=head[u];i;i=edge[i].nxt)
            if(!v[edge[i].to])
                dfs(edge[i].to,u);
    }
    
    int LCA(int x,int y){
        if(dep[x]<dep[y]) swap(x,y);
        while(dep[x]>dep[y]){
            int l=log2(dep[x]-dep[y]);
            x=f[x][l];
        }
        if(x==y) return x;
        int k=log2(dep[x])+1;
        while(k>=0){
            if(f[x][k]!=f[y][k]) 
                x=f[x][k],y=f[y][k];
            --k;
        }
        return f[x][0];
    }
    
    int main(){
        scanf("%d%d%d",&n,&m,&s);
        for(int i=1;i<=n-1;++i){
            int u,v;
            scanf("%d%d",&u,&v);
            add_edge(u,v);
            add_edge(v,u);
        }
        dfs(s,0);
        for(int i=1;i<=m;++i){
            int u,v;
            scanf("%d%d",&u,&v);
            printf("%d
    ",LCA(u,v));
        }
    }

      

      

      

      

  • 相关阅读:
    FLASH开发[00]
    slickedit使用小技巧
    网络编程[34]
    网络编程[39]
    网络编程[41]
    网络编程[37]
    网络编程[32]
    网络编程[35]
    Apache安装和配置
    网络编程[40]
  • 原文地址:https://www.cnblogs.com/lamkip/p/13287713.html
Copyright © 2020-2023  润新知