• 倍增(在线)求LCA


    这几天,提高B组总是有求LCA的题。由于我是蒟蒻,所以老是做不出来,直接上暴力。现在才弄懂。
    没耐心看前面部分的大神门可以直接看后面。
    ST(RMQ)算法(在线)求LCA


    LCA是什么?

    在一棵树上,两个节点的最近公共祖先就是LCA。

    求LCA有什么用?

    我见到最多的是,在一些题目中,我们需要找出树上两个点之间的路径,其中就要借助LCA,作为一个中转点。
    举个例子:
    我们要找出两个红色的点之间的路径。
    例子
    黄色的这条路就是我们要求的。
    例子
    怎么找?

    暴力方法1

    BFS或DFS遍历一遍。时间复杂度显然是O(N)的。

    但我们要记住,这不是图,而是一棵树!
    这是一棵树,所以每两个点之间一定有一个中转点(可能是它们本身)!
    这个中转点就是它们的最近公共祖先。(图中绿色的那个点)
    例子
    两个点之间的路径显然。


    怎么求LCA?

    暴力方法2

    先dfsO(N)记录它的父亲。
    两端同时暴力往上跳,每到一个点就打一个标记,跳到打过标记的点时退出,这个点就是LCA。
    例子
    但速度较慢。设两个点为x和y,深度为deep[x]和deep[y]。那么将最多会有abs(deep[x]-deep[y])个没有用的点被搜到(比如这个图的第5步实际上是没用的)。那么,我们能不能不搜到这些没用的点?
    当然可以!

    暴力方法3

    首先用dfsO(N)预处理出每个点的深度(它们的父亲也可以同时处理)。
    先挑一个比较深的点,往上跳到与另一个点深度相同的位置。然后两边同时往上面暴力,相遇的点即答案。另一个例子
    然而还是过不了。看看例题(LCA模板题)。这种方法只有70分。因为每次都要搜一遍,很慢。
    如果数据出了一条链来卡,就跑得超慢。

    这也不行,那怎么办?(读者:说了这么久还是在将暴力,你几个意思啊?)


    倍增求LCA

    求LCA有几种方法,在网上我见到了tarjan(离线),RMQ转LCA,还有树链剖分。我介绍一个方法,叫倍增。
    设f[i][j]表示点i往上的第2^j个祖先。
    首先我们用dfsO(NlgN)求出f数组。式子:f[i][j]=f[f[i][j-1]][j-1]。不解释。
    然后我们就可以优美地倍增啦!首先,原来的套路,将两个点跳到同一深度(跳到同一深度的过程也是几个几个跳)。然后将j从大到小枚举,若f[x][j]!=f[y][j],则跳过去。否则就别跳,不然可能会跳过LCA。
    最终的答案为f[x][0](f[y][0]一样)。因为在这种限制下,不可能出现x==y的情况,除它们在同一条链上,如下图
    在同一条链上
    这种情况可以特判。因为你在统一它们的深度后,它们就已经重合了。
    时间复杂度:O(NlgN+QlgN)
    空间复杂度:O(NlgN)
    NlgN为dfs预处理的时间,Q是询问次数。


    代码实现

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

    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    using namespace std;
    int n,m,s;
    struct EDGE
    {
        int x,y;
        EDGE* las;
    } e[1000001];//前向星存边
    int ne;
    EDGE* last[500001];
    int f[500001][21];
    int deep[500001];
    void make_tree(int,int,int);
    int main()
    {
        scanf("%d%d%d",&n,&m,&s);
        int i,x,y;
        for (i=1;i<n;++i)
        {
            scanf("%d%d",&x,&y);
            e[++ne]={x,y,last[x]};
            last[x]=e+ne;
            e[++ne]={y,x,last[y]};
            last[y]=e+ne;
        }
        make_tree(s,0,0);
        int j,k,tx,ty;
        for (i=1;i<=m;++i)
        {
            scanf("%d%d",&x,&y);
            if (deep[x]<deep[y])
                swap(x,y);//确保x为深度较大的那个点
            k=deep[x]-deep[y];
            j=0;
            while (k)
            {
                if ((k&1))
                    x=f[x][j];
                k>>=1;
                ++j;
            }//这段代码起了将两点的深度统一的作用。不知道这样打的原因的同学可以想想快速幂。当然也可以向下面那样打for。两种都可以。
            if (x==y)
            {
                printf("%d
    ",x);
                continue;
            }
            for (j=int(log2(deep[x]));j>=0;--j)//若这里像上面那样打while会错。原因不解释。
                if (f[x][j]!=f[y][j])
                {
                    x=f[x][j];
                    y=f[y][j];
                }
            printf("%d
    ",f[x][0]);
        }
    }
    void make_tree(int t,int fa,int de)
    {
        f[t][0]=fa;
        int i,j;
        for (i=1,j=2;j<=de;++i,j<<=1)
            f[t][i]=f[f[t][i-1]][i-1];//处理处f数组
        deep[t]=de;
        EDGE* ei;
        for (ei=last[t];ei;ei=ei->las)
            if (ei->y!=fa)
                make_tree(ei->y,t,de+1);
    }
  • 相关阅读:
    到底如何设置 Java 线程池的大小?
    面试一个 3 年 Java 程序员,一个问题都不会!
    Spring Boot 集成 Ehcache 缓存,三步搞定!
    牛逼哄哄的 "零拷贝" 是什么?
    一个 Java 字符串到底有多少个字符?
    不用找了,300 分钟帮你搞定 Spring Cloud!
    五分钟搞懂 Linux 重点知识,傻瓜都能学会!
    如何设计一个完美的权限管理模块?
    Redis基础都不会,好意思出去面试?
    .net c# MVC提交表单的4种方法
  • 原文地址:https://www.cnblogs.com/jz-597/p/11145312.html
Copyright © 2020-2023  润新知