P2726 [SHOI2005] 树的双中心 题解

Description

n5×104n\leq 5\times 10^4,树的深度 100\leq 100

Solution

对于每个 x,yx,y,满足 d(v,x)d(v,y)d(v,x)\leq d(v,y) 或者 d(v,x)d(v,y)d(v,x)\geq d(v,y) 的点一定构成一个子树,所以可以枚举这个子树的根,然后对两边分别求重心可以得到答案。

但是直接暴力求是 O(n2)O(n^2) 的,过不了。

注意到深度很小,所以可以考虑 O(h)O(h) 求一颗树的重心。

szisz_i 表示以 ii 为根的子树权值的和,假设已知节点 uu 到其他点的距离和 nownow,那么对于 uu 的儿子 vv 的答案就是 now+szroot2×szvnow+sz_{root}-2\times sz_v,所以每次只用跳到子树权值最大的儿子 vv,如果答案不会变优就终止即可。

容易想到预处理每个点权值最大的儿子,但是这里求总树去掉当前树的连通块的答案时有修改,又注意到一个点最多只有一个儿子的权值会变,所以只要记录权值最大和次大的儿子即可。

时间复杂度:O(nh)O(nh)

Code

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
#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 5e4 + 5;

int n;
int64_t ans = 1e18, sz[kMaxN], f[kMaxN];
int a[kMaxN], p[kMaxN], dep[kMaxN], wson1[kMaxN], wson2[kMaxN];
std::vector<int> G[kMaxN];

void dfs1(int u, int fa) {
sz[u] = a[u], p[u] = fa, dep[u] = dep[fa] + 1;
for (auto v : G[u]) {
if (v == fa) continue;
dfs1(v, u);
sz[u] += sz[v], f[u] += f[v] + sz[v];
if (sz[v] >= sz[wson1[u]]) wson2[u] = wson1[u], wson1[u] = v;
else if (sz[v] >= sz[wson2[u]]) wson2[u] = v;
}
}

int64_t dfs2(int u, int fa, int64_t now, int64_t sum, int pos) { // 找答案
int v = wson1[u];
if (v == pos || sz[wson2[u]] > sz[v]) v = wson2[u];
if (sz[v] * 2 > sum) return std::min(now, dfs2(v, u, now + (sum - sz[v] * 2), sum, pos));
else return now;
}

void dfs3(int u, int fa) {
for (auto v : G[u]) {
if (v == fa) continue;
for (int j = u; j; j = p[j]) sz[j] -= sz[v];
ans = std::min(ans, dfs2(v, u, f[v], sz[v], v) + dfs2(1, 0, f[1] - f[v] - (int64_t)(dep[v] - 1) * sz[v], sz[1], v));
for (int j = u; j; j = p[j]) sz[j] += sz[v];
dfs3(v, u);
}
}

void dickdreamer() {
std::cin >> n;
for (int i = 1; i < n; ++i) {
int u, v;
std::cin >> u >> v;
G[u].emplace_back(v), G[v].emplace_back(u);
}
for (int i = 1; i <= n; ++i)
std::cin >> a[i];
dfs1(1, 0), dfs3(1, 0);
std::cout << ans << '\n';
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

P2726 [SHOI2005] 树的双中心 题解
https://sobaliuziao.github.io/2024/01/03/post/c2b3dc43.html
作者
Egg_laying_master
发布于
2024年1月3日
许可协议