zoukankan      html  css  js  c++  java
  • 2019年7月训练(陆)

    LCA倍增法

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

    今天讲的时候有点跑神,现在卑微地来补习(菜)

    LCA指的是最近公共祖先(Least Common Ancestors)。

    最简单的算法无疑是从两个点一个个往上走,出现的第一个两个点都走过的点即为两点的LCA。

    但是时间很长。

    所以起用倍增,倍增的作用就是将两点上升所需的复杂度减低

    大致流程为:将deep不同的两个点跳到同一层,再跳到deep[lca-1]的那层,再向上跳一层就是lca了。

    加速跳的方法就是每次向上跳的层数为2的i次方层,就是把层数转化成2进制的数,这样时间复杂度就变为log2(n)了。

    之后要两个数组f[i][j](从i向上2^j层后到达的点)和deep[i](这棵树中i点的深度)。

    deep[i]用一个dfs求得。

    f[i][j]用了递推,f[i][j]=f[f[i][j-1]][j-1]。初始化f[i][0]也可以在遍历整棵树的时候求得。

    然后,两点再同时向上逼近。i从最高位开始枚举,假设两点分别为x,y,那么能向上跳的判断式为:

    if (f[x][i]!=f[y][i])
    {
           x=f[x][i];
           y=f[y][i];   
    }

    就是如果两点向上跳了2^i层以后不到同一个点就接着往上跳。为什么这样?因为如果往上跳了2^i层,即使到了同一个点,它不一定是两点的LCA。

    这样做,最终就会到达LCA的下面一层。随后,我们再将两点向上跳一层。LCA求得。

    然后这个让我无比摸不着头脑的问题出现了:

    为什么最终会到达LCA的下面一层?

    我们假设从a,b点开始,往上跳2^j层,跳到同一点。不跳。往上跳2^(j-1)层,不跳到同一点,往上跳,分别到了A',B'。显然,这种情况是一定会存在的。那么,从A',B’再往上跳到原来那个决定不跳的点,显然要跳2^(j-1)层。那么,那个点有可能是LCA,也有可能不是,对吧?所以,从A',B'往上跳到LCA所需的层数,是≤2^(j-1)的。换句话来说,A',B'到X的层数变成了一个j-2位的二进制数(可能会有前导零,也就是还可能会跳到点数相同的地方)。而此时,刚好枚举到j-2位。那么,前导零不减,再这么减下去,你发现,这个层数差最终会变成0,而你最终也会到达第X层。

    大概就是你的叔伯(爸爸的兄弟)不是你的祖先,这里找的祖先必须是直系的。

    为什么与X层数差最终会变成0?

    首先我们证明,前导零不会被减去。假设与X层的层数差为x',而你正准备往上跳y层。由于LCA的层数是X+1,而LCA往上的点它都不会跳,对吧?(反而,如果LCA往下的点,也就是层数<=X,也就是y<x'+1,它都会往上跳)所以如果y>=x'+1,那么就绝对不会往上跳。

    显然,当x'的该位为0,且属于前导零,那么只需证明x'+1<=y。而这个非常易证(假设y为10000,而x'满足条件的最大值为01111)。所以保证,前导零是不会减去的。

    接着我们证明,一旦枚举到了x'的第一个为1的位数,这个1绝对会被减去。按照同样的方法,假设y为10000,而x'满足条件的最小值为10000,所以y<x'+1.

    两点合在一起,前导零不会减去,枚举到一个1位就减去,最终这个层数差就会变成0.证毕。

    代码:

    #include<cstdio>
    #include<cstring>
    #include<stack>
    #include<algorithm>
    #include<cmath>
    #define  maxn 500010
    #include<queue>
    using namespace std;
    
    int f[maxn][21],head[maxn],deep[maxn],cnt,n,m,rt;
    struct edge
    {
        int v,next;
    }e[maxn<<1];
    
    void add(int u,int v)
    {
        e[++cnt].v=v;
        e[cnt].next=head[u];
        head[u]=cnt;
    }
    
    void dfs(int x,int fa)
    {
        f[x][0]=fa;
        deep[x]=deep[fa]+1;
        for(int i=head[x];i;i=e[i].next)
        {
            int v=e[i].v;
            if(v==fa) continue;
            dfs(v,x);
        }
    }
    
    void Init()
    {
        for(int j=1;(1<<j)<=n;++j)
        {
            for(int i=1;i<=n;++i)
            {
                if(deep[i]>=(1<<j))
                {
                    f[i][j]=f[f[i][j-1]][j-1];
                }
            }
        }
    }
    
    int query(int x,int y)
    {
        if(deep[x]<deep[y]) swap(x,y);
        int d=deep[x]-deep[y];
        for(int j=20;j>=0;--j) if(d&(1<<j)) x=f[x][j];
        if(x==y) return x;
        for(int j=20;j>=0;--j)
        {
            if(f[x][j]!=f[y][j])
            {
                x=f[x][j];
                y=f[y][j];
            }
        }
        return f[x][0];
    }
    
    int main()
    {
        int u,v;
        scanf("%d%d%d",&n,&m,&rt);
        
        for(int i=1;i<n;++i)
        {
            scanf("%d%d",&u,&v);
            add(u,v);
            add(v,u);
        }
        
        dfs(rt,0);
        Init();
        while(m--)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            printf("%d
    ",query(x,y));
        }
        return 0;
    }

    2019-07-3122:54:32

  • 相关阅读:
    如何解决Pulling without specifying how to reconcile divergent branches
    Mac设置终端打开快捷键
    Mac 息屏快捷键
    Windows安装使用Openssl
    tomcat证书转换成nginx证书。jks/keystore > crt/key
    Windows下类似Linux的CAT命令是什么
    齐文词根词缀---3.23、co-(放在元音前面)表示共同,(和com和con一个意思)
    齐文词根词缀---3.22、clus-关闭(就是close)
    齐文词根词缀---3.21、clam/claim-喊
    齐文词根词缀---3.20、cis-切、割(同cid)
  • 原文地址:https://www.cnblogs.com/plzplz/p/11279752.html
Copyright © 2011-2022 走看看