[BZOJ 3302][Shoi2005]树的双中心:TreeDP

来源:互联网 发布:mac 贴膜 涂层脱落 编辑:程序博客网 时间:2024/06/15 23:44

点击这里查看原题

首先可以想到n^2做法,枚举每一条边,切断这条边变成两棵树,对两棵树各O(n)求一遍重心,加和即为答案
但是题目中有一个重要条件,高度h不超过100,因此可以O(h)求重心,即每次向权值和最大的儿子转移(维护时需要维护最大和次大)。
总复杂度O(nh)

/*User:SmallLanguage:C++Problem No.:3302*/#include<bits/stdc++.h>#define ll long longusing namespace std;const int M=50005;const ll inf=(ll)1<<60;int n,tot=1,fir[M],w[M],cut,mx[M][2],dep[M],fa[M];ll g[M],ans=inf,sum[M],sumall,a,b;struct edge{    int v,nex;}e[M<<1];void add(int u,int v){    e[++tot]=(edge){v,fir[u]};    fir[u]=tot;}void dfs(int u,int f){    fa[u]=f;    for(int i=fir[u];i;i=e[i].nex){        int v=e[i].v;        if(v==f) continue;        dep[v]=dep[u]+1;        dfs(v,u);        sum[u]+=sum[v];        g[u]+=g[v]+sum[v];        if(sum[v]>sum[mx[u][0]]) mx[u][1]=mx[u][0],mx[u][0]=v;        else if(sum[v]>sum[mx[u][1]]) mx[u][1]=v;    }}void getcen(int tp,int u,ll k,ll &res){    res=min(res,k);    int v=mx[u][0];    if(v==cut||sum[mx[u][1]]>sum[mx[u][0]]) v=mx[u][1];    if(!v) return;    getcen(tp,v,k+sum[tp]-2*sum[v],res);}void solve(int u){    for(int i=fir[u];i;i=e[i].nex){        int v=e[i].v;        if(v==fa[u]) continue;        cut=v;        for(int k=u;k;k=fa[k]) sum[k]-=sum[v];        a=b=inf;        getcen(1,1,g[1]-g[v]-dep[v]*sum[v],a);        getcen(v,v,g[v],b);        ans=min(ans,a+b);        for(int k=u;k;k=fa[k]) sum[k]+=sum[v];        solve(v);    }}int main(){    freopen("data.in","r",stdin);//    scanf("%d",&n);    for(int i=1;i<n;i++){        int u,v;        scanf("%d%d",&u,&v);        add(u,v);        add(v,u);    }    for(int i=1;i<=n;i++) scanf("%lld",&sum[i]);    dfs(1,0);    solve(1);    printf("%lld\n",ans);    return 0;}