[知识点]最近公共祖先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

(暂略)

posted @ 2015-11-01 18:36  jinkun113  阅读(727)  评论(0编辑  收藏  举报