【BZOJ】4033: [HAOI2015]树上染色 树上背包

【题目】#2124. 「HAOI2015」树上染色

【题意】给定n个点的带边权树,要求将k个点染成黑色,使得 [ 黑点的两两距离和+白点的两两距离和 ] 最大。n<=2000。

【算法】树上背包

【题解】设f[i][j]表示子树i中有j个黑点对答案的贡献(包括点 i 到父亲的边 p ),由于边p的贡献只和 j 有关,所以最后再统计。

所以做树上背包即可,注意这题特殊在f[x][0]≠0,所以初始f[x][k]+=f[y][0],然后不要把0作为物品。

最后统计边p的贡献:w[p] *(子树内黑点*子树外黑点+子树内白点*子树外白点)。

常数问题:要尽可能避免枚举无用状态,不然常数太大了,优化见代码。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=2010,inf=0x3f3f3f3f;
struct edge{int v,w,from;}e[maxn*2];
int first[maxn],tot,n,K,sz[maxn];
ll f[maxn][maxn];
void insert(int u,int v,int w){tot++;e[tot].v=v;e[tot].w=w;e[tot].from=first[u];first[u]=tot;}
ll max(ll a,ll b){return a<b?b:a;}
void dfs(int x,int fa,int w){
    for(int i=2;i<=K;i++)f[x][i]=-inf;
    sz[x]=1;
    for(int i=first[x];i;i=e[i].from)if(e[i].v!=fa){
        dfs(e[i].v,x,e[i].w);
        sz[x]+=sz[e[i].v];
        for(int k=min(sz[x],K);k>=0;k--){
            f[x][k]+=f[e[i].v][0];//
            for(int j=min(k,sz[e[i].v]);j>=1;j--)if(f[x][k-j]>-inf){//
                f[x][k]=max(f[x][k],f[x][k-j]+f[e[i].v][j]);
            }else break;
        }
    }
    for(int i=0;i<=K;i++)if(f[x][i]>-inf)f[x][i]+=1ll*w*(1ll*i*(K-i)+1ll*(sz[x]-i)*(n-K-sz[x]+i));
}
int main(){
    scanf("%d%d",&n,&K);
    for(int i=1;i<n;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        insert(u,v,w);insert(v,u,w);
    }
    dfs(1,0,0);
    printf("%lld",f[1][K]);
    return 0;
}
View Code

 

posted @ 2018-03-13 09:24  ONION_CYC  阅读(...)  评论(...编辑  收藏