LCA 的第不知道多少种求法
我发烧了。所以等我意识到我写了什么之后已经不太想删掉了。
为了解决求 LCA 的问题,我独立发现了如下两个非常重要的性质。
-
性质
1 :两个点的 LCA 在这两个点的简单路径上。 -
性质
2 :两个点的 LCA 在这两个点的简单路径上深度是最小的,同时其深度在该路径上是唯一的。
我突然想到,在树上找两点中间点权最小的点是一个经典问题。所以,我构建了一个树链剖分,可以在单次
同时我们惊喜地发现,在第一次 dfs 的时候我们已经处理出来了每个点的深度,就不需要再单独去求一次了,第二次 dfs 的时候可以直接把求出来的深度放到线段树上。
然后,我们就可以在
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 5e5 + 5;
const int INF = 1e9;
int n, m, s;
vector<int> adj[MAXN];
// 树链剖分相关变量
int parent[MAXN], depth[MAXN], siz[MAXN], heavy[MAXN], top[MAXN], pos[MAXN];
int current_pos;
// 线段树相关变量
int val[MAXN], idx_pos[MAXN]; // val和idx_pos数组,pos映射到原节点
int min_val[4 * MAXN], min_idx[4 * MAXN];
// 第一次DFS,计算parent, depth, siz, heavy
void dfs1(int u, int p) {
parent[u] = p;
depth[u] = depth[p] + 1;
siz[u] = 1;
heavy[u] = -1;
int max_size = 0;
for (int v : adj[u]) {
if (v != p) {
dfs1(v, u);
siz[u] += siz[v];
if (siz[v] > max_size) {
max_size = siz[v];
heavy[u] = v;
}
}
}
}
// 第二次DFS,计算top, pos,并填充val和idx_pos
void dfs2(int u, int top_node) {
top[u] = top_node;
pos[u] = current_pos;
idx_pos[current_pos] = u;
val[current_pos] = depth[u];
current_pos++;
if (heavy[u] != -1) {
dfs2(heavy[u], top_node);
for (int v : adj[u]) {
if (v != parent[u] && v != heavy[u]) {
dfs2(v, v);
}
}
}
}
// 构建线段树
void build(int node, int l, int r) {
if (l == r) {
min_val[node] = val[l];
min_idx[node] = idx_pos[l];
return;
}
int mid = (l + r) / 2;
build(2 * node, l, mid);
build(2 * node + 1, mid + 1, r);
if (min_val[2 * node] <= min_val[2 * node + 1]) {
min_val[node] = min_val[2 * node];
min_idx[node] = min_idx[2 * node];
} else {
min_val[node] = min_val[2 * node + 1];
min_idx[node] = min_idx[2 * node + 1];
}
}
// 线段树区间查询,返回最小值及其索引
pair<int, int> query(int node, int l, int r, int ql, int qr) {
if (qr < l || ql > r) {
return {INF, -1};
}
if (ql <= l && r <= qr) {
return {min_val[node], min_idx[node]};
}
int mid = (l + r) / 2;
pair<int, int> left = query(2 * node, l, mid, ql, qr);
pair<int, int> right = query(2 * node + 1, mid + 1, r, ql, qr);
if (left.first <= right.first) {
return left;
} else {
return right;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m >> s;
for (int i = 0; i < n - 1; ++i) {
int u, v;
cin >> u >> v;
adj[u].push_back(v);
adj[v].push_back(u);
}
// 第一次DFS初始化
depth[0] = -1; // 虚拟根节点的深度为-1
dfs1(s, 0);
// 第二次DFS初始化
current_pos = 0;
dfs2(s, s);
// 构建线段树
build(1, 0, n - 1);
while (m--) {
int u, v;
cin >> u >> v;
vector<pair<int, int>> intervals;
// 拆分路径为多个区间
while (top[u] != top[v]) {
if (depth[top[u]] > depth[top[v]]) {
swap(u, v);
}
intervals.emplace_back(pos[top[v]], pos[v]);
v = parent[top[v]];
}
if (depth[u] > depth[v]) {
swap(u, v);
}
intervals.emplace_back(pos[u], pos[v]);
int res_val = INF;
int res_idx = -1;
// 查询每个区间的最小值
for (auto &p : intervals) {
int l = p.first;
int r = p.second;
auto [current_min, current_idx] = query(1, 0, n - 1, l, r);
if (current_min < res_val || (current_min == res_val && current_idx < res_idx)) {
res_val = current_min;
res_idx = current_idx;
}
}
cout << res_idx << "\n";
}
return 0;
}