bzoj3302

树形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\n", ans);
    return 0;
}
View Code

 

posted @ 2017-11-04 13:27  19992147  阅读(168)  评论(0编辑  收藏  举报