树形dp
很明显我们可以枚举一条边,然后求两边的重心,这样是暴力,我们用一些奇怪的方法来优化这个找重心的过程,我们先预处理出来每个点最大和第二的儿子,然后每次把断掉的子树的贡献减掉,每次找重心就是向最大或第二大的儿子走,如果最大的儿子被减掉后比第二大的儿子小或者这条边被剪掉了,那么就向第二大的儿子走,这样复杂度是h的
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 5e4 + 5; int n; ll ans = 1e18; vector<int> G[N]; int dep[N], a[N], fa[N], son[N], bro[N]; ll sum[N], size[N]; void dfs(int u, int last) { size[u] = a[u]; fa[u] = last; for(int i = 0; i < G[u].size(); ++i) { int v = G[u][i]; if(v == last) continue; dep[v] = dep[u] + 1; dfs(v, u); size[u] += size[v]; sum[u] += sum[v] + size[v]; if(!son[u] || size[v] > size[son[u]]) bro[u] = son[u], son[u] = v; else if(!bro[u] || size[v] > size[bro[u]]) bro[u] = v; } } void center(ll &ret, int ban, ll tot, int u, ll S) { ret = min(ret, S); int v = son[u]; if(v == ban || size[bro[u]] > size[v]) v = bro[u]; if(!v) return; center(ret, ban, tot, v, S + tot - 2 * size[v]); } void solve(int u, int last) { for(int i = 0; i < G[u].size(); ++i) { int v = G[u][i]; if(v == last) continue; for(int x = u; x; x = fa[x]) size[x] -= size[v]; ll tmp1 = 1e16, tmp2 = 1e16; center(tmp1, v, size[1], 1, sum[1] - sum[v] - size[v] * dep[v]); center(tmp2, 1, size[v], v, sum[v]); ans = min(ans, tmp1 + tmp2); for(int x = u; x; x = fa[x]) size[x] += size[v]; solve(v, u); } } int main() { scanf("%d", &n); 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); } for(int i = 1; i <= n; ++i) scanf("%lld", &a[i]); if(n <= 2) { puts("0"); return 0; } dfs(1, 0); solve(1, 0); printf("%lld ", ans); return 0; }