• 【模板】LCA


    (十一集训前最后的挣扎)

    先介绍LCA是啥吧。。

    LCA:Lowest Common Ancestors(最近公共祖先)

    用来求树上任意两点的最近相同父亲节点,有各种不同的方法,这里先介绍树上倍增求LCA(另一种我不会。。)

    先看一道题:(RP++)

    这是翻译:

    先看看朴素算法

    先依次向上查找x的祖先,存入xa数组,再依次向上查找y的祖先,与xa数组中的值比较,第一个相同的就是x,y的最近公共祖先。

    每一次查找的时间复杂度是O(n)

    (好慢。。)

    树上倍增就是将查找的次数减少,每一次都尽可能多的往上走,大大减少查找次数。

    这里倍增的倍就是指2的多少次方,但是倍增总要有个上限,对于有n层的树,上限就是log2n

    流程图(摘自老师的PPT):

    要怎么用代码实现呢。。

    先看流程图:

     

    出现了一个令我迷茫的东西----位运算符“&”

    这个东西应该怎么用呢?

    比如:1010 0011& 0000 1111,结果为0000 0011。也就是与上0相当于把那位数清0,与上1相当于把那位保留。(摘自百度知道)

    (有点像快速幂。。)

    但是只用单纯的树上倍增真的可以解决这道题吗?

    当x,y不在同一层的时候就会有一点难处理。

    这时我们可以先让在更深层的x或y先跳到与另一个节点同一层,再利用树上倍增求LCA。

    大概思路就是这样。

    看看代码?(想看建树的话请看SPFA

    #include<cstdio>
    #include<iostream>
    #include<cmath>
    using namespace std;
    const int maxn = 500005;
    const int maxe = 1000005;
    int n,m,root;
    
    struct line{
        int from,to;
        line(){}//空构造函数 line p; 
        line(int A,int B){
            //构造函数 line L=line(1,2);
            from=A;to=B;
        }
    };
    line edge[maxe];
    int last[maxn],_next[maxe],e; 
    //last[x]表示以x为起点的最后一条边(的编号) 
    //_next[i]表示与第i条边起点相同的上一条边(的编号) 
    void add_edge(int x,int y){
        edge[++e]=line(x,y);
        _next[e]=last[x];
        last[x]=e;
    }
    int Fa[maxn][35],Dep[maxn];
    void dfs(int x,int fa){
        int i,k,y;
        Fa[x][0]=fa;
        Dep[x]=Dep[Fa[x][0]]+1;                       //记录当前节点的深度 
        k=ceil(log(Dep[x])/log(2));                      //x往上倍增的上限 
        for(i=1;i<=k;i++)Fa[x][i]=Fa[Fa[x][i-1]][i-1];  //倍增计算祖先 
        for(int i=last[x];i;i=_next[i]){
            int v=edge[i].to;
            if(v!=fa)dfs(v,x); 
        }
    }
    int LCA(int x,int y){
        int i,k,s;
        s=ceil(log(n)/log(2));                 //该树倍增最大可能的上限 
        if(Dep[x]<Dep[y])swap(x,y);      //交换x和y的值 
        /////////////x往上走k层,让x与y处于同一层 //////////
        k=Dep[x]-Dep[y];
        for(i=0;i<=s;i++)
            if(k&(1<<i))x=Fa[x][i];
        if(x==y)return x;                     //x==y时,x就是最近公共祖先 
        ///////////////////////////////////////////////////
        s=ceil(log(Dep[x])/log(2));           //计算向上倍增的上限 
        for(i=s;i>=0;i--)
            if(Fa[x][i]!=Fa[y][i]){ x=Fa[x][i]; y=Fa[y][i]; }
        return Fa[x][0];
    }
    
    int main(){
        int i,j,k;
        cin>>n>>m>>root;
        for(i=1;i<n;i++){
            int x,y;
            scanf("%d%d",&x,&y);
            add_edge(x,y);
            add_edge(y,x);
        }
        dfs(root,0);
        for(i=1;i<=m;i++){
            int x,y;
            scanf("%d%d",&x,&y);
            printf("%d
    ",LCA(x,y));
        }
            
    } 

     关于核心代码的循环过程:

    为什么有If(fa[u][i]!=fa[v][i]) { u=fa[u][i],v=fa[v][i]; } 这一句呢?不是当他们相等时就可以结束了吗?

    原理:当fa[u][i]==fa[v][i]时,所求的节点不一定是最近的公共祖先。

    没有注释的点这里

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    using namespace std;
    struct edge{
        int next,to;
        edge(){}
        edge(int a,int b)
        {
            next=a;
            to=b;
        }
    }e[1000001];
    int f[100001][31],dep[100001],first[100001],tot;
    void add_edges(int a,int b)
    {
     e[++tot]=edge(first[a],b);
     first[a]=tot;
    }
    
    void dfs(int x,int fa)
    {
        f[x][0]=fa;
        dep[x]=dep[fa]+1;
        int k=ceil(log(dep[x])/log(2));
        for(int i=1;i<=k;i++)
        {
            f[x][i]=f[f[x][i-1]][i-1];
        }
        for(int i=first[x];i;i=e[i].next)
        {
            int pos=e[i].to;
            if(pos!=fa)
            dfs(pos,x);
        }
    }
    int n,m,root;
    int LCA(int x,int y)
    {
       if(dep[x]<dep[y])
       swap(x,y);
       int k1=dep[x]-dep[y];
       int k2=ceil(log(n)/log(2));
       for(int i=0;i<=k2;i++)
       {
            if(k1&(1<<i))
            x=f[x][i];
       }
       if(x==y)
       return x;
       int k3=ceil(log(dep[x])/log(2));
       for(int i=k3;i>=0;i--)
       {
          if(f[x][i]!=f[y][i])
          {
               x=f[x][i];y=f[y][i];
            }
        }
        return f[x][0];
    }
    int main(){
        int i,j,k;
        cin>>n>>m>>root;
        for(i=1;i<n;i++){
            int x,y;
            scanf("%d%d",&x,&y);
            add_edges(x,y);
            add_edges(y,x);
        }
        dfs(root,0);
        for(i=1;i<=m;i++){
            int x,y;
            scanf("%d%d",&x,&y);
            printf("%d
    ",LCA(x,y));
        }
            
    } 
    RP++!

    (RP++!)

  • 相关阅读:
    将Excel嵌入你的.Net程序
    调用资源文件
    Socket Error# Description
    LPCTSTR 和其它
    linux send and recv详解
    stdafx.h的作用
    setsocketopt() usage
    openfire源码入门级分析
    openfire分析
    关于xmpp
  • 原文地址:https://www.cnblogs.com/Daz-Os0619/p/11469720.html
Copyright © 2020-2023  润新知