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

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

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

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


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

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

图1

  1 T

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

  1 F B

序号 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 ) 表示 u 结点的第一次出现的位置,那么我们根据上面的表格就可以得到 n 个结点各自的 f i r s t 值:

  2 f i r s t

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

根据深度优先遍历的特点,我们可以知道,对于结点 u 和结点 v , 我们不妨假设 u v 之前被遍历,也就是说 f i r s t ( u ) < f i r s t ( v ) ,那么在 u 遍历到 v 过程中深度最小的点就是 L C A ( 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 ) )

举个栗子:

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

  3 F B

序号 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 ~ 12 这个串呢?原因很简单, f i r s t ( 6 ) = 5 f i r s t ( 8 ) = 12 ,这个值上面的表已经给出了。

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

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

具体操作就看代码。

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])];
    }
}

猜你喜欢

转载自blog.csdn.net/Diogenes_/article/details/81412316
今日推荐