链接:https://www.luogu.org/problemnew/show/P3177
题目描述
有一棵点数为 N 的树,树边有边权。给你一个在 0~ N 之内的正整数 K ,你要在这棵树中选择 K个点,将其染成黑色,并将其他 的N-K个点染成白色 。 将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间的距离的和的受益。问受益最大值是多少。
输入输出格式
输入格式:
第一行包含两个整数 N, K 。接下来 N-1 行每行三个正整数 fr, to, dis , 表示该树中存在一条长度为 dis 的边 (fr, to) 。输入保证所有点之间是联通的。
输出格式:
输出一个正整数,表示收益的最大值。
输入输出样例
说明
对于 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 ", dp[1][k]); return 0; }