P3177 [HAOI2015]树上染色

链接:https://www.luogu.org/problemnew/show/P3177

题目描述

有一棵点数为 N 的树,树边有边权。给你一个在 0~ N 之内的正整数 K ,你要在这棵树中选择 K个点,将其染成黑色,并将其他 的N-K个点染成白色 。 将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间的距离的和的受益。问受益最大值是多少。

输入输出格式

输入格式:

 

第一行包含两个整数 N, K 。接下来 N-1 行每行三个正整数 fr, to, dis , 表示该树中存在一条长度为 dis 的边 (fr, to) 。输入保证所有点之间是联通的。

 

输出格式:

 

输出一个正整数,表示收益的最大值。

 

输入输出样例

输入样例#1: 复制
3 1
1 2 1
1 3 2
输出样例#1: 复制
3

说明

对于 100% 的数据, 0<=K<=N <=2000

题解:摘自洛谷

正确的状态应该是,dp[u][i]表示以u为跟的子树中,选择i个黑节点,对答案有多少贡献

为什么是说“对答案有多少贡献呢”?

主要是想到一点,即分别考虑每条边对答案的贡献

即,边一侧的黑节点数*另一侧的黑节点数*边权+一侧的白节点数*另一侧的白节点数*边权

这点很容易证明,但是不容易想到(原因是我太弱了)

然后情况就明了了,整个问题成了一个树形背包,考虑每个子节点分配多少个黑色节点(体积),然后算出这条边对答案的贡献(价值)

这里再一次强调“贡献”,是因为这个贡献不只是在当前子树内,而是对于整棵树来说的

转移方程为dp[u][i] = max( dp[u][i], dp[u][i-j] + dp[v][j] + val )

其中v为u的子节点,j为在这个子节点中选择的黑色点的个数,val为这条边的贡献

val = j*(k-j)*w + (sz[v]-j)*(n-k+j-sz[v])*w

其中w为这条边的边权,n为总的节点数,k为总的需要选择的黑色节点数,sz[v]为以v为根的子树的节点数量

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn = 2005;
int h[maxn], siz[maxn], n, k, tot, now=0;
LL dp[maxn][maxn];
struct edge{
    int v, nxt;
    LL w;
}G[maxn<<1];
void add(int u, int v, LL w){
    G[++tot].v = v;
    G[tot].nxt = h[u];
    h[u] = tot;
    G[tot].w = w;
}
void dfs(int u, int f){
    siz[u] = 1;
    dp[u][0] = dp[u][1] = 0;
    for(int i = h[u]; i; i = G[i].nxt){
        int v = G[i].v;
        if(v == f)continue;
        dfs(v, u);
        siz[u] += siz[v];
    }
    for(int i = h[u]; i; i = G[i].nxt){
        int v = G[i].v;
        if(v == f)continue;
           for(int j = min(k, siz[u]); j >= 0; j--)
              for(int kk = 0; kk <= min(j, siz[v]); kk++){
                 LL val =  1LL* G[i].w * ((k-kk)*kk + (n-k-siz[v]+kk)*(siz[v]-kk));
                 if(dp[u][j-kk] >=0)
                    dp[u][j] = max(dp[u][j], dp[u][j-kk] + dp[v][kk] + val);
        }
 
 
    }
 
 
}
 
int main()
{
    scanf("%d%d", &n, &k);
    memset(dp, -1, sizeof(dp));
    for(int i = 1; i < n; i++){
        int u, v;
        LL w;
        scanf("%d%d%lld", &u, &v, &w);
        add(u, v, w);
        add(v, u, w);
    }
    dfs(1, -1);
    printf("%lld\n", dp[1][k]);
    return 0;
}
View Code

 

 
posted @ 2018-07-23 10:12  Ed_Sheeran  阅读(312)  评论(0编辑  收藏  举报