Description :
有一棵点数为 N 的树,树边有边权。给你一个在 0~ N 之内的正整数 K ,你要在这棵树中选择 K个点,将其染成黑色,并将其他 的N-K个点染成白色 。 将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间的距离的和的受益。问受益最大值是多少。
Solution:
这道题的思路很好,数据较强,可以看出这是道树形dp
怎么设计状态呢?dp[x][j]表示以x为根的子树有j个点染成黑色的最大收获
然后,然后就GG了
原来这道题是考虑贡献,强啊
状态改为以x的根的子树,有j个点染成黑色的最大贡献,这个贡献是对这个棵树而言的
转移比较明显 枚举 j ,枚举子节点 的 黑色点数 k,直接dp即可
注意枚举顺序,我们需要dp[x][j - k]未更新的贡献,就像01背包,一样倒序枚举j,正序枚举k,用j-k更新j,从更小的更新,它的阶段一定是上一个,不会出现自己更新自己,具有拓扑性
注意边界:f[x][0]=f[x][1]=0;,不会出现成对的所以没有贡献
#include <cstring>
#include <cstdio>
#include <iostream>
using namespace std;
const int MAXX = 2020;
long long f[MAXX][MAXX];
int hed[MAXX], ver[MAXX << 1], nxt[MAXX << 1], edge[MAXX << 1], siz[MAXX];
int n, cnt, tot;
inline void add(int x, int y,int z) {
ver[++tot] = y;
nxt[tot] = hed[x];
hed[x] = tot;
edge[tot] = z;
}
void dfs(int x, int fa) {
siz[x] = 1;
f[x][0] = f[x][1] = 0;
for (int i = hed[x]; i; i = nxt[i]) {
int y = ver[i];
if (y == fa) continue;
dfs(y,x);
siz[x] += siz[y];
}
for (int i = hed[x]; i; i = nxt[i]) {
int y = ver[i];
if (y == fa) continue;
for (int j = min(siz[x],cnt); j >= 0; --j) {
for (int k = 0;k <= min(siz[y], j); ++k) {
if(f[x][j - k] < 0) continue;
long long val = (long long)k * (cnt - k) * edge[i] + (long long)(siz[y] - k) * (n - cnt - siz[y] + k) * edge[i];
f[x][j] = max(f[x][j], f[x][j - k] + f[y][k] + val);
}
}
}
}
int main() {
scanf("%d%d", &n, &cnt);
for (int i = 1; i < n; ++i) {
int x, y, z;
scanf("%d%d%d",&x, &y, &z);
add(x, y, z);
add(y, x, z);
}
memset(f,-1,sizeof(f));
dfs(1, 0);
printf("%lld",f[1][cnt]);
return 0;
}
%%%
syt我是巨佬