完整部分点这里
平常在信息学竞赛中求LCA一般有三种办法:
上述三种算法都比较常用,这篇文章主要介绍第二种解法。
先介绍一下欧拉序:
对有根树T进行深度优先遍历,无论是递归还是回溯,每次到达一个节点就把编号记录下来,得到一个长度为 2 N − 1 2N - 1 2N−1 的序列,成为树 T 的欧拉序列F。
如图1(Inkscape手画,略丑轻喷~):
[外链图片转存失败(img-bLDJAzZw-1562835893221)(https://s1.ax2x.com/2018/08/05/55YtmN.png)]
图 1 − 有 根 树 T 图 \ 1 - 有根树T 图 1−有根树T
按照深度优先遍历我们可以得到它的欧拉序和深度序列:
表 1 − 欧 拉 序 列 F 和 深 度 序 列 B 表 \ 1 - 欧拉序列 F 和深度序列 B 表 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 ) 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值 表 2−各元素的first值
序号 | 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' 表 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 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])];
}
}