zoukankan      html  css  js  c++  java
  • [知识点]最近公共祖先LCA

    UPDATE(20180822):重写部分代码。

    1、前言

      最近公共祖先(LCA),作为树上问题,应用非常广泛,而求解的方式也非常多,复杂度各有不同,这里对几种常用的方法汇一下总。

    2、基本概念和暴力算法

      最近公共祖先,顾名思义,指的是两个点的公有祖先中,最近的那个点。它显然不会作为一个单独的知识点拿出来考,但是在很多题目中,出现的频率很高,不同场合用不同的方法。先考虑最简单的做法。找祖先,显然是从所查询的两个点从下往上爬,直到两个点出现第一次重叠时,就是最近公共祖先了。首先我们预处理出所有点的深度及父亲节点。由于两个点深度可能不同,首先我们要保证深度大的点先往上爬到与另一个点深度相同的地方,然后两个点同时向上爬,直到出现重叠。代码如下:

    int lca_n() {
        if (d[x] < d[y]) swap(x, y);
        while (d[x] != d[y]) x = fa[x];
        while (x != y) x = fa[x], y = fa[y];
        return x;
    }

    3、倍增LCA

      倍增这个概念听名字很好理解,知道倍增思想的,直接运用到求LCA并不是不好理解。首先我们需要预处理出所有节点的各种祖宗关系,p[i][j]记录节点i的第2^j个祖先,j = 0时,就是他的父亲节点。同上述暴力算法,我们从询问的两个点开始,往他们的这些倍数祖先向上爬,若第2^i个祖先不同,则从第2^(i - 1)个祖先开始,从头开始倍增。时间复杂度为 log 级别。代码如下:

    void prep() {
        for (int i = 1; i <= n; i++) p[i][0] = fa[i];
        for (int j = 1; (1 << j) <= n; j++)
            for (int i = 1; i <= n; i++) 
                if (p[i][j - 1]) p[i][j] = p[p[i][j - 1]][j - 1];
    }
    
    int lca_h() {
        if (d[x] < d[y]) swap(x, y);
        while (d[x] != d[y]) {
            int o = 1;
            while (d[p[x][o]] > d[y]) o++;
            x = p[x][o - 1];
        }
        while (x != y) {
            int o = 1;
            while (p[x][o] != p[y][o]) o++; 
            x = p[x][o - 1], y = p[y][o - 1];
        }
        return x;
    }

    4、树链剖分LCA

      树链剖分详细概念见(http://www.cnblogs.com/jinkun113/p/4683299.html)。为什么树链剖分可以跑LCA?原文提得很清楚,在把重链划分出来之后,可以确定的是重链要么单独一条存在,要么必定相连,故可以用线段树来维护。

      树链剖分的优势和倍增是一样的,都可以跳过一些不必要的部分,若当前点的重链顶端不是父亲节点,可以利用线段树直接从当前点跳到顶端。这样可以达到减小复杂度的目的。

      这里写出树链剖分DFS预处理和LCA核心代码的最新版本。代码到树链剖分那篇文章中找吧。

    5、Tarjan LCA

    (暂略)

  • 相关阅读:
    poj3436(ACM Computer Factory)
    一位ACMer过来人的心得
    poj1459(Power Network)
    (转)网络流—最大流(Edmond-Karp算法)
    poj1611(The Suspects)
    构建之法阅读笔记01
    第三周总结
    全国疫情可视化地图
    第二周总结
    作业--数组(大数)
  • 原文地址:https://www.cnblogs.com/jinkun113/p/4928374.html
Copyright © 2011-2022 走看看