zoukankan      html  css  js  c++  java
  • 关于求LCA三种方法

    这里介绍LCA的三种方法(倍增,RMQ,tarjan) ps:以lg3379为例.

    1.倍增

    可以理解为暴力的优化.先让两个点跳在同一高度,再一起跳2^k次倍祖先.

    难点:倍增数组转移方程:bz[i][j]=bz[bz[i][j-1]][j-1]

    #include<iostream>
    #include<cstdio>
    using namespace std;
    #define R register
    #define e exit(0)
    const int maxn=5e6+10;
    int n,m,k,s,cnt,head[maxn],fa[maxn],h[maxn],bz[maxn][30];
    struct shu{
        int to,next;
    }tree[maxn*2];
    inline int fd()
    {
        int s=1,t=0;
        char c=getchar();
        while(c<'0'||c>'9')
        {
            if(c=='-')
                s=-1;
            c=getchar();
        }
        while(c>='0'&&c<='9')
        {
            t=t*10+c-'0';
            c=getchar();
        }
        return s*t;
    }
    void add(int from,int to)
    {
        tree[++cnt].to=to;
        tree[cnt].next=head[from];
        head[from]=cnt;
    }
    void dfs(int x)
    {   
        for(R int k=head[x];k;k=tree[k].next)
        {
            int to=tree[k].to;
            if(!h[to])
            {
                fa[to]=x;
                h[to]=h[x]+1;
                dfs(to);
            }
        }
    }
    int main()
    {
        freopen("s.in","r",stdin);
        freopen("s.out","w",stdout);
        n=fd(),m=fd(),s=fd();
        for(R int i=1;i<=n-1;++i)
        {
            int from=fd(),to=fd();
            add(from,to),add(to,from);
        }
        h[s]=1,fa[s]=s;
        dfs(s);
        for(R int i=1;i<=n;++i)
            bz[i][0]=fa[i];//为fa[i],不为i,因为2^0==1;
        for(R int j=1;j<=19;++j)
            for(R int i=1;i<=n;++i)
                bz[i][j]=bz[bz[i][j-1]][j-1];
        for(R int i=1;i<=m;++i)
        {
            int x=fd(),y=fd();
            if(h[x]<h[y])
                swap(x,y);
            for(R int k=19;k>=0;--k)
                if(h[bz[x][k]]>=h[y])
                    x=bz[x][k];
            if(x==y)
            {
                printf("%d
    ",x);
                continue;
            }
            for(R int k=19;k>=0;--k)
                if(bz[x][k]!=bz[y][k])
                    x=bz[x][k],y=bz[y][k];
                //如果bz[x][k]==bz[y][k],说明这个点是祖先但不一定是LCA,我们贪心地认为这不是LCA以求最优.
                    //其次这不是一级一级跳,是在原来以跳的2^n次方上再跳2^m次方.
            printf("%d
    ",fa[x]);//bz[x][k]==bz[y][k]就退出,bz[x][k]未赋值给x,bz[y][k]未赋值给y,所以输出bz的又上一级,即fa[x]或fa[y];
        }
        return 0;
    }

    2.RMQ

    提前说明这种算法比较慢,且很容易错.写在这里便于对RMQ的理解复习.

    大概思路是建树后跑一遍dfs以求dfn,用数组记录点在dfn中的第一次出现的位置.

    LCA一定在这两个点的dfn区间内,用RMQ求区间深度最小即LCA.有一点说

    明的是在构建RMQ时用数组用同样的方式记录最小值对应的点.

    #pragma GCC optimize("Ofast")
    #pragma GCC optimize("inline")
    #include<iostream>
    #include<cstdlib>
    #include<cstdio>
    #include<cmath>
    using namespace std;
    #define R register
    const int maxn=5*1e6+10;
    int n,m,s,cnt,tot,logn[maxn],head[maxn],dfn[maxn],h[maxn],fir[maxn],f[maxn][30],rec[maxn][30];
    struct bian{
        int to,next;
    }len[maxn*2];
    inline int fd()
    {
        int s=1,t=0;
        char c=getchar();
        while(c<'0'||c>'9')
        {
            if(c=='-')
                s=-1;
            c=getchar();
        }
        while(c>='0'&&c<='9')
        {
            t=t*10+c-'0';
            c=getchar();
        }
        return s*t;
    }
    void add(int from,int to)
    {
        len[++cnt].to=to;
        len[cnt].next=head[from];
        head[from]=cnt;
    }
    void dfs(int x,int dep)
    {
        fir[x]=++tot,dfn[tot]=x,h[tot]=dep;
        for(R int k=head[x];k;k=len[k].next)
        {
            int to=len[k].to;
            if(!fir[to])
            {
                dfs(to,dep+1);
                dfn[++tot]=x,h[tot]=dep;//回溯也要记录.
            }
        }
    }
    int main()
    {
        freopen("s.in","r",stdin);
        freopen("s.out","w",stdout);
        n=fd(),m=fd(),s=fd();
        for(R int i=1;i<n;++i)
        {
            int from=fd(),to=fd();
            add(from,to),add(to,from);
        }
        dfs(s,1);
        logn[0]=-1;
        for(R int i=1;i<=tot;++i)
            f[i][0]=h[i],rec[i][0]=dfn[i],logn[i]=logn[i>>1]+1;
        for(R int j=1;j<=20;++j)
            for(R int i=1;i+(1<<j)-1<=tot;++i)
            {
                if(f[i][j-1]<f[i+(1<<j-1)][j-1])
                {
                    f[i][j]=f[i][j-1];
                    rec[i][j]=rec[i][j-1];
                }
                else{
                    f[i][j]=f[i+(1<<j-1)][j-1];
                    rec[i][j]=rec[i+(1<<j-1)][j-1];//跟着变化记录点.
                }
            }
        for(R int i=1;i<=m;++i)
        {
            int x=fd(),y=fd();
            int l=fir[x],r=fir[y];
            if(l>r)
                swap(l,r);
            int num=logn[r-l+1];
            if(f[l][num]<f[r-(1<<num)+1][num])
                printf("%d
    ",rec[l][num]);
            else printf("%d
    ",rec[r-(1<<num)+1][num]);
        }
        return 0;
    }

    3.tarjan

    提前说明为离线算法,复杂度O(n+q),较快.ps:q为询问次数.

    大概思路为构建已经存在的树与询问树.先dfs遍历已经存在的树,

    再从遍历的节点中寻找询问树中与其关联的节点.看询问树中的节点是否

    遍历过,遍历过就一定走过了LCA,用并查集维护最开始的爸爸则是LCA.

    其中疑问,最开始的爸爸不是根结点吗?不,根节点的儿子还未遍历回去,还未认这个爸爸.

    以此类推,存在树中的所有子树结构均满足这中关系.

    #include<iostream>
    #include<cstdio>
    using namespace std;
    #define R register
    const int maxn=1e6+10;
    int n,m,s,cnt,qcnt,fa[maxn],head[maxn],qhead[maxn],vis[maxn],lca[maxn*2];
    struct bian{
        int to,next;
    }len[maxn*2];
    struct qbian{
        int to,next;
    }qlen[maxn*2];
    inline int fd()
    {
        int s=1,t=0;
        char c=getchar();
        while(c<'0'||c>'9')
        {   
            if(c=='-')
                s=-1;
            c=getchar();
        }
        while(c>='0'&&c<='9')
        {
            t=t*10+c-'0';
            c=getchar();    
        }
        return s*t;
    }
    void add(int from,int to)
    {
        len[++cnt].to=to;
        len[cnt].next=head[from];
        head[from]=cnt;
    }
    void qadd(int from,int to)
    {
        qlen[++qcnt].to=to;
        qlen[qcnt].next=qhead[from];
        qhead[from]=qcnt;
    }
    int find(int x)
    {
        if(x==fa[x])
            return x;
        else return fa[x]=find(fa[x]);
    }
    void tarjan(int x)
    {
        vis[x]=1;
        for(R int k=head[x];k;k=len[k].next)
        {
            int to=len[k].to;
            if(vis[to])
                continue;
            tarjan(to);
            fa[to]=x;//未回溯,未认father.
        }
        for(R int k=qhead[x];k;k=qlen[k].next)
        {
            int to=qlen[k].to;
            if(vis[to])
            {
                lca[k]=find(to);
                if(k%2)
                    lca[k+1]=lca[k];
                else
                    lca[k-1]=lca[k];
                //两个为一组. 
            }
        }
    }
    int main()
    {
        freopen("s.in","r",stdin);
        freopen("s.out","w",stdout);
        n=fd(),m=fd(),s=fd();
        for(R int i=1;i<=n;++i) 
            fa[i]=i;
        for(R int i=1;i<n;++i)
        {
            int from=fd(),to=fd();
            add(from,to),add(to,from);
        }   
        for(R int i=1;i<=m;++i)
        {
            int from=fd(),to=fd();
            qadd(from,to),qadd(to,from);
        }
        tarjan(s);  
        for(R int i=1;i<=m;++i)
            printf("%d
    ",lca[i*2]);
        return 0;
    }
  • 相关阅读:
    http 请求code状态码
    uni-app实现弹窗遮罩
    小程序分享报错 Cannot read property 'apply' of null;at page XXX onShareAppMessage function
    mac下出现xcrun: error导致git、svn无法使用的解决办法
    uniapp 小程序实现自定义底部导航栏(tarbar)
    Python2.X和Python3.X中Tkinter模块的文件对话框、下拉列表的不同
    Mac 设置git的template
    每天一点点之vue框架开发
    uni-app开发小程序-使用uni.switchTab跳转后页面不刷新的问题
    小程序实现微信朋友圈时间显示效果
  • 原文地址:https://www.cnblogs.com/xqysckt/p/11194904.html
Copyright © 2011-2022 走看看