zoukankan      html  css  js  c++  java
  • 倍增求lca(模板)

    定义
    LCA,最近公共祖先,是指一棵树上两个节点的深度最大的公共祖先。也可以理解为两个节点之间的路径上深度最小的点。
    我们这里用了倍增的方法求了LCA。
    我们的基本的思路就是,用dfs遍历求出所有点的深度。f[i][j]数组用来求的是距离节点i,距离2^j的祖先。可以知道,f[i][0]就是它的直接父亲。然后通过倍增的思路求出father数组的所有元素。然后进行lca。求lca的基本思路是:先让深度较大的点向上跳,
    然后x和y再同时向上跳2的幂,总会跳到这样两个点,他们的父亲结点是同一个点,那就是x和y的LCA。

    首先我们需要用邻接表建一颗参天大树~

    重头戏——倍增。

    int dep[maxn],f[maxn][64];
    /*dep数组用来记录当前点的深度
    f[i][j]代表距离i 2^j的祖先
    */

    深度和直接父亲:

    void pre(int u,int fa)
    {
        dep[u]=dep[fa]+1; //更新深度
        f[u][0]=fa;//更新父亲结点
        for(int i=1;(1<<i)<=dep[u];i++){//预处理出f数组
            f[u][i]=f[f[u][i-1]][i-1]; //这个转移可以说是算法的核心之一
            //u的2^(i-1)级祖先的2^(i-1)级祖先就是u的2^i级祖先。
        }
        for(int i=head[u];i;i=e[i].next){//遍历邻接表
            int to=e[i].to;
            if(to==f[u][0]) continue;//如果to是u的父亲,那么就说明这条边被访问过了,不能再回溯了
            pre(to,u);//继续深度优先遍历
        }
    }

    有的预处理工作都完成了。我们开始求LCA~

    int lca(int x,int y)
    {
        if(dep[x]<dep[y]) swap(x,y);//让x的深度更深
        int cha=dep[x]-dep[y];//cha是x和y的深度之差
        for(int i=0;(1<<i)<=cha;i++){
            if((1<<i)&cha){
                x=f[x][i];
            }
        }//让x跳到跟y相同高度上
        if(x!=y){//如果a和b不是同一个结点那么就要继续跳,如果是同一个结点,那么它就是LCA
            for(int i=(int)log2(n);i>=0;i--){//从大到小跳。正确性显然。
                if(f[x][i]!=f[y][i]){//如果不相等,就说明该节点的深度还是比LCA大
                    x=f[x][i];
                    y=f[y][i];
                    //那就继续跳
                }
            }
            x=f[x][0];
            //这个时候x和y还不是同一个节点,但是x和y的父亲就是x和y的lca。
        }
        return x;
    }

    常数优化:另外预处理出lg避免重复调用log函数

    for(int i=1;i<=n;i++)
            lg[i]=lg[i-1]+(1<<lg[i-1]==i);

    P3379 【模板】最近公共祖先(LCA)

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

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

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

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

    对于100%的数据:N<=500000,M<=500000

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    #include<algorithm>
    #define num ch-'0'
    #define rep(i,k,n) for(int i=k;i<=n;++i)
    using namespace std;
    int n,m,s,cnt=0;
    int head[1000005],dep[1000005],fa[1000005][64],lg[1000005];
    inline void get(int &res)
    {
        char ch;bool flag=0;
        while(!isdigit(ch=getchar()))
            (ch=='-')&&(flag=true);
        for(res=num;isdigit(ch=getchar());res=res*10+num);
        (flag)&&(res=-res);
    }
    struct node
    {
        int to,nex;
    }e[1000005];
    inline void add(int x,int y)
    {
        e[++cnt].nex=head[x];
        e[cnt].to=y;
        head[x]=cnt;
    }
    void init(int u,int f)
    {
        dep[u]=dep[f]+1;
        fa[u][0]=f;
        for(int i=1;(1<<i)<=dep[u];++i)
            fa[u][i]=fa[fa[u][i-1]][i-1];
        for(int i=head[u];i;i=e[i].nex)
        {
            if(e[i].to==f) continue;
            init(e[i].to,u);
        }
    }
    int lca(int x,int y)
    {
        if(dep[x]<dep[y]) swap(x,y);
        while(dep[x]>dep[y])
            x=fa[x][lg[dep[x]-dep[y]]-1];
        if(x!=y)
        {
            for(int i=lg[dep[x]];i>=0;--i)
                if(fa[x][i]!=fa[y][i])
                {
                    x=fa[x][i];
                    y=fa[y][i];
                }
            x=fa[x][0];
        }
        return x;
    }
    int main()
    {
        get(n),get(m),get(s);
        rep(i,1,n-1)
        {
            int x,y;
            get(x),get(y);
            add(x,y);add(y,x);
        }
        init(s,0);
        rep(i,1,n) lg[i]=lg[i-1]+(1<<lg[i-1]==i);
        rep(i,1,m)
        {
            int x,y;
            get(x),get(y);
            printf("%d
    ",lca(x,y));
        }
        return 0;
    } 

    参考https://blog.csdn.net/qq_42386465/article/details/82978520

  • 相关阅读:
    java基础知识——7.断点调试与数据加密
    Hibernate最基础配置的记忆方式
    Redis集群命令
    VMware Workstation下centos的使用
    Redis入门
    Shell脚本
    使用Java遇到的问题
    使用Linux
    压缩包安装MySQL服务
    安装windows+ubuntu双系统
  • 原文地址:https://www.cnblogs.com/mxrmxr/p/9816824.html
Copyright © 2011-2022 走看看