洛谷P3379 【模板】最近公共祖先(LCA)『用欧拉序列转化为 RMQ 问题』解法
题目链接:https://www.luogu.com.cn/problem/P3379
解题思路:
对一棵树进行 DFS,无论是第一次访问还是回溯,每次到达一个结点时都将编号记录下来,可以得到一个长度为 \(2n-1\) 的序列,这个序列被称作这棵树的欧拉序列。
在下文中,把结点 u 在欧拉序列中第一次出现的位置编号记为 \(pos(u)\)(也称作节点 u 的欧拉序),把欧拉序列本身记作 \(E[1..2n-1]\)。
有了欧拉序列,LCA 问题可以在线性时间内转化为 RMQ 问题,即 \(pos(LCA(u, v))=\min\{pos(k)|k\in E[pos(u)..pos(v)]\}\)。
这个等式不难理解:从 \(u\) 走到 \(v\) 的过程中一定会经过 \(LCA(u,v)\),但不会经过 \(LCA(u,v)\) 的祖先。因此,从 \(u\) 走到 \(v\) 的过程中经过的欧拉序最小的结点就是 \(LCA(u, v)\)。
用 DFS 计算欧拉序列的时间复杂度是 \(O(n)\),且欧拉序列的长度也是 \(O(n)\),所以 LCA 问题可以在 \(O(n)\) 的时间内转化成等规模的 RMQ 问题。
示例程序:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;
int st[maxn][20], n, m, rt, pos[maxn], ord[maxn], idx, id[maxn];
vector<int> g[maxn];
void build_st() {
for (int j = 0; (1<<j) <= idx; j++) {
for (int i = 1; i+(1<<j)-1 <= idx; i++) {
if (!j)
st[i][j] = pos[ ord[i] ];
else
st[i][j] = min(st[i][j-1], st[i+(1<<j-1)][j-1]);
}
}
}
int query(int l, int r) {
int m = log2(r - l + 1);
return id[min(st[l][m], st[r-(1<<m)+1][m])];
}
void dfs(int u, int p) {
ord[++idx] = u;
if (!pos[u]) {
pos[u] = idx;
id[idx] = u;
}
for (auto v : g[u]) {
if (v != p) {
dfs(v, u);
ord[++idx] = u;
}
}
}
int main() {
scanf("%d%d%d", &n, &m, &rt);
for (int i = 1; i < n; i++) {
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs(rt, -1);
assert(idx == 2*n-1);
build_st();
while (m--) {
int u, v;
scanf("%d%d", &u, &v);
int l = pos[u], r = pos[v];
if (l > r) swap(l, r);
printf("%d\n", query(l, r));
}
return 0;
}
浙公网安备 33010602011771号