• LCA 模板


    关于LCA:

      LCA 指树上两点的公共祖先。

    如何 “暴力” 找两点的 LCA :

      可以先 DFS 一遍求出每个点的 dep (深度)。然后从深度大的点先往上跳,跳到与另一个点相同的深度,如果还没有到达相同的,就两个点一起往上跳,直到达到相同的点,那么,这个点就是两点的 LCA 。

    关于倍增法求 LCA :

      其实就是一个经过优化的暴力算法。让两个点一次向上跳多步来优化时间。

    如何实现倍增求 LCA :

      设 f [ u ][ k ] 表示 u 的 2k 辈祖先,即从 u 向根节点走 2k 步到达的节点。特别地,若该节点不存在,则令 f [ u ][ k ] = 0 。f [ u ][ 0 ] 就是 x 的父节点。因为 u 向根节点走 2k ⇔ 向根节点走 2k-1 步,再走 2k-1 步。所以对于 k∈ [ 1,logn ] ,有 f [ u ][ k ] = f [ f [ u ][ k-1 ] ][ k-1 ]。

      f 数组利用了递推的思想。递推式为: f [ u ][ k ] = f [ f [ u ][ k-1 ] ][ k-1 ]。因此,我们可以对树进行遍历 DFS ,由此得到 f [ u ][ 0 ],再计算出 f 数组的所有值。(预处理的期望复杂度为 O(nlogn))。

      在预处理完之后可以多次对不同的 x,y 计算 LCA ,每次询问的时间复杂度为 O(logn)。

      基于 f 数组(假装已经预处理)计算 LCA( x, y ) 分为以下几步:

      ①设 dep [ x ] 表示 x 的深度。那么设 dep [ x ] ≥ dep [ y ] 。(否则,可交换 x, y )

      ②利用二进制拆分的思想,把 x 向上调整到与 y 同一的深度。

      即:依次尝试从 x 向上走 k = 2logn… 21,20 步,若到达的点比 y 深,则令 x = f [ x ][ k ]。

      ③若此时的 x = y ,则说明已经找到了 LCA ,两点的 LCA 就等于 y 。

      ④若此时的 x ≠ y ,那么 x, y 同时向上调整,并保持深度一致且二者不会相会。

      具体来说就是,依次尝试把 x, y 同时向上走 k = 2logn… 21,20 步,若 f [ x ][ k ] ≠ f [ y ][ k ](即仍未相会),则令 x = f [ x ][ k ],y = f [ y ][ k ]。

      ⑤此时 x,y 必定只差一步就相会了,他们的父节点 f [ x ][ 0 ] 就是 LCA。

    倍增求 LCA 的伪代码:

    预处理:

    inline void Deal_first(int u,int fa)
    {
        dep[u]=dep[fa]+1;//深度+1 
        for(int i=0;i<=19;i++)//2^0 ~ 2^19 
            f[u][i+1]=f[f[u][i]][i];//递推公式,上面讲过了。
        for(int i=head[u];i;i<=t[i].nex)//前向星遍历(相当于dfs) 
        {
            int v=t[i].to;//记录子节点 
            if(v==fa) continue;//防止倒退(因为是无向边) 
            f[v][0]=u;//子节点向上跳一步就是父节点 
            Deal_first(v,u);//v-子节点,u-父节点 
        }
    }

    查询 x,y 的 LCA:

    inline int LCA(int x,int y)
    {
        if(dep[x]<dep[y]) swap(x,y);//让x深度较大
        //用“暴力”的思想:先让x,y跳到同一深度,然后一起往上跳 
        for(int i=20;i>=0;i--)//倒着for,x能多跳尽量多跳 ,才能优化时间 
        {
            if(dep[f[x][i]]>=dep[y]) x=f[x][i];//先跳到同一层 
            if(x==y) return y;
        }
        for(int i=20;i>=0;i--)//此时x,y已跳到同一层 
        {
            if(f[x][i]!=f[y][i])//如果 f[x][i]和f[y][i]不同才跳 
            {
                x=f[x][i];
                y=f[y][i];
            }
        }
        return f[x][0];//跳完上述步骤后,两点离LCA仅一步之遥,让x(或y)再向上跳一步就是LCA。 
    }

    倍增求LCA的板子题

    题目描述:

      如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。

    输入输出格式:

    输入格式:

      第一行包含三个正整数 N、M、S,分别表示树的结点个数、询问的个数和树根结点的序号。

      接下来 N-1 行每行包含两个正整数 x、y,表示 x 结点和 y 结点之间有一条直接连接的边(数据保证可以构成树)。

      接下来 M 行每行包含两个正整数 a、b,表示询问 a 结点和 b 结点的最近公共祖先。

    输出格式:

      输出包含 M 行,每行包含一个正整数,依次为每一个询问的结果。

    #include<cstdio>
    #include<cmath>
    #include<cstdlib>
    #include<cstring>
    #include<string>
    #include<iostream>
    #include<algorithm>
    #include<queue>
    #include<stack>
    #include<deque>
    #include<set>
    #include<map>
    #include<vector>
    #include<fstream>
    using namespace std;
    #define maxn 501000
    int n,m,s;
    int dep[maxn<<1];
    int f[maxn<<1][21];
    int head[maxn<<1],cnt=0;
    struct hh
    {
        int nex,to;
    }t[maxn<<1];
    inline void add(int nex,int to)
    {
        t[++cnt].nex=head[nex];
        t[cnt].to=to;
        head[nex]=cnt;
    }
    inline void Deal_first(int u,int fa)
    {
        dep[u]=dep[fa]+1;
        for(int i=0;i<20;i++)
            f[u][i+1]=f[f[u][i]][i];
        for(int i=head[u];i;i=t[i].nex)
        {
            int v=t[i].to;
            if(v==fa) continue;
            f[v][0]=u;
            Deal_first(v,u);
        }
    return;
    }
    inline int LCA(int x,int y)
    {
        if(dep[x]<dep[y]) swap(x,y);
        for(int i=20;i>=0;i--)
        {
            if(dep[f[x][i]]>=dep[y]) x=f[x][i];
            if(x==y) return x;
        }
        for(int i=20;i>=0;i--)
        {
            if(f[x][i]!=f[y][i])
            {
                x=f[x][i];
                y=f[y][i];
            }
        }
        return f[x][0];
    }
    inline int read()
    {
        int kr=1,xs=0;
        char ls;
        ls=getchar();
        while(!isdigit(ls))
        {
            if(!(ls^45))
                kr=-1;
            ls=getchar();
        }
        while(isdigit(ls))
        {
            xs=(xs<<1)+(xs<<3)+(ls^48);
            ls=getchar();
        }
        return xs*kr;
    }
    int main()
    {
        int x,y;
        n=read();m=read();s=read();//n个节点,m个询问,以s为根节点 
        for(int i=1;i<n;i++)
        {
            x=read();y=read();
            add(x,y);
            add(y,x);//添加无向边 
        }
        Deal_first(s,0);//以点s为根节点预处理 f 数组 
        for(int i=1;i<=m;i++)
        {
            x=read();y=read();
            printf("%d
    ",LCA(x,y));
        }
    return 0;
    }
  • 相关阅读:
    十二、curator recipes之双重屏障DoubleBarrier
    十一、curator recipes之联锁InterProcessMultiLock
    十、curator recipes之信号量InterProcessSemaphoreV2
    九、curator recipes之不可重入锁InterProcessSemaphoreMutex
    八、curator recipes之选举主节点LeaderSelector
    五、curator recipes之选举主节点Leader Latch
    ADO.net 数据库连接new SqlConnection、Open、Close、Dispose
    Java学习笔记【八、数据结构】
    Java学习笔记【七、时间、日期、数字】
    Java学习笔记【六、正则表达式】
  • 原文地址:https://www.cnblogs.com/lck-lck/p/9746245.html
Copyright © 2020-2023  润新知