平常在信息学竞赛中求LCA一般有三种办法:
- 用倍增法求解,预处理复杂度是 ,每次询问的复杂度是 , 属于在线解法。
- 利用欧拉序转化为RMQ问题,用 ST表 求解RMQ问题,预处理复杂度 ,每次询问的复杂度为 , 也是在线算法。
- 采用Tarjan算法求解,复杂度 ,属于离线算法。
上述三种算法都比较常用,这篇文章主要介绍第二种解法。
先介绍一下欧拉序:
对有根树T进行深度优先遍历,无论是递归还是回溯,每次到达一个节点就把编号记录下来,得到一个长度为
的序列,成为树 T 的欧拉序列F。
如图1(Inkscape手画,略丑轻喷~):
按照深度优先遍历我们可以得到它的欧拉序和深度序列:
序号 | 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 |
为了方便,我们定义 表示 结点的第一次出现的位置,那么我们根据上面的表格就可以得到 个结点各自的 值:
序号 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
first() | 1 | 2 | 8 | 10 | 3 | 5 | 11 | 12 |
根据深度优先遍历的特点,我们可以知道,对于结点 和结点 , 我们不妨假设 在 之前被遍历,也就是说 ,那么在 遍历到 过程中深度最小的点就是 (这一点在Tarjan算法中也有运用)。
这样一来,我们就可以将 LCA 问题转化为RMQ问题: 。
举个栗子:
仍然以上图为例,假定 , 在图上很明显能够看出 。同时我们把代表从 遍历到 的这个序列”抽“出来:
序号 | 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 |
为什么要从原序列中抽取 ~ 这个串呢?原因很简单, , ,这个值上面的表已经给出了。
可以看出,在这个序列中,深度最小的点的序号是 和 其值是 ,这不就是我们要的 吗!
有了这个例子大家应该就很清楚了,具体实现的时候还要注意 表中存的不再只是那个最小值,而是最小值的下标,也就是表中的序号。
具体操作就看代码。
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])];
}
}