牛客 contest 12986E Shortest Path Sum

题目描述

https://ac.nowcoder.com/acm/contest/12986/E

简要题意:给定一个 $n$ 个点的基环树,现在有 $m$ 次询问,每次询问给定三元组 $(x,y,z)$,求一个点 $k$,使得 $d(x,k)+d(y,k)+d(z,k)$ 最小,$d(x,y)$ 表示 $x$ 到 $y$ 的最短路径

$n\le 10^5$

Solution

如果不是基环树,那么答案显然是 $\frac{d(x,y)+d(x,z)+d(y,z)}{2}$,否则我们按照一种比较神奇的建树方法,建三棵树,然后枚举 $x,y,z$ 分别在哪棵树上即可

建图大概是这样:

xzkRzj.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include <iostream>
#include <cstdio>
#include <vector>
#include <stack>
#include <algorithm>
#define maxn 300010
#define INF 1000000000
using namespace std;

int n, m;

int fa[maxn];
void init_fa(int n) { for (int i = 1; i <= n; ++i) fa[i] = i; }

int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }

inline void merge(int x, int y) {
int fx = find(x), fy = find(y);
if (fx == fy) return ;
fa[fx] = fy;
}

struct Edge {
int to, next;
} e[maxn * 2]; int c1, head[maxn];
inline void add_edge(int u, int v) {
e[c1].to = v; e[c1].next = head[u]; head[u] = c1++;
}

int dep[maxn], f[maxn][21];
void dfs(int u, int fa) {
for (int i = head[u]; ~i; i = e[i].next) {
int v = e[i].to; if (v == fa) continue;
dep[v] = dep[u] + 1; f[v][0] = u; dfs(v, u);
}
}

void init_lca(int n) {
for (int j = 1; j <= 20; ++j)
for (int i = 1; i <= n; ++i) f[i][j] = f[f[i][j - 1]][j - 1];
}

int get_lca(int x, int y) {
if (dep[x] < dep[y]) swap(x, y);
for (int i = 20; ~i; --i)
if (dep[f[x][i]] >= dep[y]) x = f[x][i];
if (x == y) return x;
for (int i = 20; ~i; --i)
if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
return f[x][0];
}

inline int D(int x, int y) {
return dep[x] + dep[y] - 2 * dep[get_lca(x, y)];
}

inline int Dis(int x, int y, int z) {
return D(x, y) + D(x, z) + D(y, z) >> 1;
}

int main() { fill(head, head + maxn, -1);
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);

cin >> n >> m; init_fa(n);
for (int i = 1; i <= n; ++i) {
int x, y; cin >> x >> y;
if (find(x) != find(y)) {
for (int k = 0; k < 3; ++k)
add_edge(x + k * n, y + k * n), add_edge(y + k * n, x + k * n);
merge(x, y);
}
else {
add_edge(x, y + n), add_edge(y + n, x);
add_edge(x + n, y + 2 * n), add_edge(y + 2 * n, x + n);
}
} dfs(1, 0); init_lca(3 * n);
while (m--) {
int x, y, z; cin >> x >> y >> z;
int ans = INF;
for (int i = 0; i < 3; ++i)
for (int j = 0; j < 3; ++j)
for (int k = 0; k < 3; ++k)
ans = min(ans, Dis(x + i * n, y + j * n, z + k * n));
cout << ans << "\n";
}
return 0;
}