1. 题面梗概
中译中: 在一棵带有节点权值 W(v) 的树上选两个中心点 x 和 y,最小化所有点到距离其最近中心的权值距离之和。
即最小化下方柿子:
S(x,y)=v∈V∑(W(v)⋅min{d(v,x),d(v,y)})
2. 逻辑推导
既然是两个点,我们就假设这两个点为 A 和 B,由于每个点都会找到一个最近的位置,不妨将图上的节点划分为两个集合 A 和 B。
而树有一个特别的性质,n 个点只有 n−1 条边,所以我们可以把问题转化为,删除树上的某一条边,然后分别求出两棵子树的重心。
2.1 重心
这题麻烦的一点在于,点是存在点权的,没法直接套用 重心的模板。
重心的定义由“子树大小”演变为“子树权值和”。
但是同 P5666 树的重心 的逻辑,带权重心在树上具有单调移动的性质。
nxt_val=cur_val+(sz_sum−sz[v])−sz[v]=cur_val+sz_sum−2sz[v]
结论:只要某个 子树权值和 sz[v] 满足 sz_sum<2sz[v],那么向该方向移动一定会让总距离变小。(公式写的很明白了)
2.2 路径
理论上这题也可以采用倍增优化,但是题目限定了 H≤100,所以我们可以直接暴力向下搜索,沿着满足条件的边找就可以了。
复杂度为 O(N⋅H),在这个数据规模下,很稳定。(我甚至都怀疑是出题人故意为之的)
再补充一点小细节,因为高度不大,所以这图没必要先找到根节点,再建图。直接以 1 为根节点跑一遍就行了,每次切割 (u,v) 之后,将图分为 1→u 和 v→n 两个连通块处理即可。
2.3 结论
是重心性质的变体,带权重心。
3. 代码实现
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 90 91 92 93 94 95 96 97 98 99 100 101 102 103
| #include <bits/stdc++.h> using namespace std; using ll = long long;
const int maxn = 5e4 + 50; const ll inf = 1e18;
struct Edge { int u, v; };
vector< int > adj[maxn]; vector< Edge > edges;
ll sz[maxn], val[maxn]; int dep[maxn], fa[maxn], w[maxn]; int son1[maxn], son2[maxn]; int n;
void dfs( int u, int p, int d ) { sz[u] = w[u]; val[u] = 0; dep[u] = d; fa[u] = p; son1[u] = son2[u] = 0; for( int v : adj[u] ) { if( v == p ) continue; dfs( v, u, d + 1 );
sz[u] += sz[v]; val[u] += val[v] + sz[v];
if( sz[v] > sz[son1[u]] ) son2[u] = son1[u], son1[u] = v; else if( sz[v] > sz[son2[u]] ) son2[u] = v; } }
void calc( int u, ll cur_val, int sz_sum, int del, ll &res ) { res = min( res, cur_val );
int son = son1[u]; if( son1[u] == del || ( son2[u] && sz[son2[u]] > sz[son1[u]] ) ) son = son2[u];
if( son == del || son == 0 ) return;
if( sz[son] * 2 > sz_sum ) { calc( son, cur_val + sz_sum - 2 * sz[son] , sz_sum, del, res ); }
}
void solve( ) { cin >> n;
for( int i = 1; i < n; ++ i ) { int u, v; cin >> u >> v; adj[u].push_back( v ); adj[v].push_back( u ); edges.push_back({ u, v }); }
for( int i = 1; i <= n; ++ i ) cin >> w[i];
dfs( 1, 0, 0 );
ll ans = inf;
for( auto [u, v] : edges ) { if( fa[v] != u ) swap( u, v );
for( int cur = u; cur; cur = fa[cur] ) sz[cur] -= sz[v];
ll res_up = inf, res_down = inf;
ll cost = val[1] - ( val[v] + sz[v] * dep[v] );
calc( 1, cost, sz[1], v, res_up ); calc( v, val[v], sz[v], 0, res_down );
ans = min( ans, res_up + res_down );
for( int cur = u; cur; cur = fa[cur] ) sz[cur] += sz[v]; } cout << ans << '\n'; }
int main( ) { ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); solve( ); return 0; }
|
复盘
-
复杂度分析:O(N⋅H)。
-
碎碎念:在有权图中,重心的判定条件是 max(szchild,sztotal−szu)≤sztotal/2。
-
关联笔记:直径与重心