【模板】LCA(欧拉序+RMQ)

完整部分点这里

平常在信息学竞赛中求LCA一般有三种办法:

  • 用倍增法求解,预处理复杂度是 O ( n log ⁡ n ) O(n\log n) O(nlogn) ,每次询问的复杂度是 O ( log ⁡ n ) O(\log n) O(logn), 属于在线解法。
  • 利用欧拉序转化为RMQ问题,用 ST表 求解RMQ问题,预处理复杂度 O ( n + n log ⁡ n ) O(n + n \log n) O(n+nlogn) ,每次询问的复杂度为 O ( 1 ) O(1) O(1), 也是在线算法。
  • 采用Tarjan算法求解,复杂度 O ( α ( n ) + Q ) O(\alpha(n) + Q) O(α(n)+Q) ,属于离线算法。

上述三种算法都比较常用,这篇文章主要介绍第二种解法。


先介绍一下欧拉序:
对有根树T进行深度优先遍历,无论是递归还是回溯,每次到达一个节点就把编号记录下来,得到一个长度为 2 N − 1 2N - 1 2N1 的序列,成为树 T 的欧拉序列F。

如图1(Inkscape手画,略丑轻喷~):

[外链图片转存失败(img-bLDJAzZw-1562835893221)(https://s1.ax2x.com/2018/08/05/55YtmN.png)]

图   1 − 有 根 树 T 图 \ 1 - 有根树T  1T

按照深度优先遍历我们可以得到它的欧拉序和深度序列:

表   1 − 欧 拉 序 列 F 和 深 度 序 列 B 表 \ 1 - 欧拉序列 F 和深度序列 B  1FB

序号 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
欧拉序列F 1 2 5 2 6 2 1 3 1 4 7 8 7 4 1
深度序列B 1 2 3 2 3 2 1 2 1 2 3 4 3 2 1

为了方便,我们定义 f i r s t ( u ) first(u) first(u) 表示 u u u 结点的第一次出现的位置,那么我们根据上面的表格就可以得到 n n n 个结点各自的 f i r s t first first 值:

表   2 − 各 元 素 的 f i r s t 值 表 \ 2 - 各元素的first值  2first

序号 1 2 3 4 5 6 7 8
first() 1 2 8 10 3 5 11 12

根据深度优先遍历的特点,我们可以知道,对于结点 u u u 和结点 v v v, 我们不妨假设 u u u v v v 之前被遍历,也就是说 f i r s t ( u ) < f i r s t ( v ) first(u) < first(v) first(u)<first(v) ,那么在 u u u 遍历到 v v v 过程中深度最小的点就是 L C A ( u , v ) LCA(u, v) LCA(u,v) (这一点在Tarjan算法中也有运用)。

这样一来,我们就可以将 LCA 问题转化为RMQ问题: L C A ( T , u , v ) = R M Q ( B , f i r s t ( u ) , f i r s t ( v ) ) LCA(T, u, v) = RMQ(B, first(u), first(v)) LCA(T,u,v)=RMQ(B,first(u),first(v))

举个栗子:

仍然以上图为例,假定 u = 6 u = 6 u=6 , v = 8 v = 8 v=8 在图上很明显能够看出 L C A ( u , v ) = 1 LCA(u, v) = 1 LCA(u,v)=1 。同时我们把代表从 u u u 遍历到 v v v 的这个序列”抽“出来:

表   3 − 抽 取 的 欧 拉 序 列 F ‘ 和 深 度 序 列 B ′ 表 \ 3 - 抽取的欧拉序列 F‘ 和深度序列 B'  3FB

序号 5 6 7 8 9 10 11 12
欧拉序列F’ 6 2 1 3 1 4 7 8
深度序列B’ 3 2 1 2 1 2 3 4

为什么要从原序列中抽取 5 5 5 ~ 12 12 12 这个串呢?原因很简单, f i r s t ( 6 ) = 5 first(6) = 5 first(6)=5 f i r s t ( 8 ) = 12 first(8)=12 first(8)=12 ,这个值上面的表已经给出了。

可以看出,在这个序列中,深度最小的点的序号是 7 7 7 9 9 9 其值是 1 1 1 ,这不就是我们要的 L C A ( u , v ) LCA(u, v) LCA(u,v) 吗!

有了这个例子大家应该就很清楚了,具体实现的时候还要注意 S T ST ST 表中存的不再只是那个最小值,而是最小值的下标,也就是表中的序号。

具体操作就看代码。

namespace LCA {
    int ST[MAXN << 1][LOG], value[MAXN << 1], depth[MAXN << 1], first[MAXN], dist[MAXN], cnt;
    inline int calc(int x, int y) {
        return depth[x] < depth[y] ? x : y;
    }
    inline void dfs(int u, int p, int d) {
        value[++cnt] = u; depth[cnt] = d; first[u] = cnt;
        for (int i = head[u]; i; i = edge[i].next) {
            int v = edge[i].to;
            if (v == p) continue;
            dist[v] = dist[u] + edge[i].dist;
            dfs(v, u, d + 1);
            value[++cnt] = u; depth[cnt] = d;
        }
    }
    inline void init(int root, int node_cnt) {
    	cnt = 0; dist[root] = 0;
        dfs(root, 0, 1);
        int n = 2 * node_cnt - 1;
        for (int i = 1; i <= n; i++) ST[i][0] = i;
        for (int j = 1; j < LOG; j++) 
            for (int i = 1; i + (1 << j) - 1 <= n; i++) 
                ST[i][j] = calc(ST[i][j - 1], ST[i + (1 << (j - 1))][j - 1]);
    }
    inline int query(int x, int y) {
        int l = first[x], r = first[y];
        if (l > r) std::swap(l, r);
        int k = log2(r - l + 1);
        return value[calc(ST[l][k], ST[r - (1 << k) + 1][k])];
    }
}

你可能感兴趣的:(LCA,RMQ)