[HAOI2015] 树上染色
题目大意:有一棵点数为\(N\)的树,树边有边权,你要在这棵树中选择 \(K\)个点,将其染成黑色,并将其他的\(N-K\)个点染成白色,将所有点染色后,会获得黑点两两之间的距离加上白点两两之间的距离的和的受益.问受益最大值是多少.$ 0\leq K\leq N \leq2000$
Solution
普通的定义\(f[i][j]\)为以\(i\)为根,选取\(j\)个黑点,所得的最大收益是不行的,不宜于转移
我们考虑下面一种定义
- 状态:\(f[i][j]\)为以\(i\)为根,选取\(j\)个黑点的总贡献
这就有点抽象了,我们打个比方,假设要选\(1\)个黑点,树的形态是三个点的链,我们现在到了叶子节点,那么这个结点被选为黑点所产出的贡献是节点内的白子数量(0)乘上节点外的白子数量(2),加上结点内的黑子数量(1)乘上结点外的黑子数量(0),再乘上此叶子结点连到父亲的边权,贡献是0,因为当此节点被选为黑点,黑白双方都不可能经过这个结点
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 2005;
int ecnt, n, k;
int head[N], siz[N];
long long f[N][N];
struct Edge {
int to, dis, val, next;
}e[N << 1];
inline void adde(int x, int y, int z) {
e[++ecnt].to = y;
e[ecnt].next = head[x];
e[ecnt].val = z;
head[x] = ecnt;
}
void dfs(int x, int y, int z) {
f[x][0] = f[x][1] = 0, siz[x] = 1;
for(int i = head[x]; i; i = e[i].next) {
if(e[i].to == y) continue;
dfs(e[i].to, x, e[i].val);
for(int j = min(siz[x], k); j >= 0; --j)
for(int m = min(siz[e[i].to], k - j); m >= 0; --m) {
f[x][j + m] = max(f[x][j + m], f[e[i].to][m] + f[x][j]);
}
siz[x] += siz[e[i].to];
}
for(int j = 0; j <= min(siz[x], k); ++j) {
f[x][j] += 1LL * z * (j * (k - j) + (siz[x] - j) * (n - k - siz[x] + j));//加上到父节点的边的贡献
}
}
inline int read() {
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9') {
if (c == '-') f = -1;
c = getchar();
}
while (c >= '0' && c <= '9') {
x = (x << 3) + (x << 1) + (c ^ 48);
c = getchar();
}
return x * f;
}
int main() {
scanf("%d %d", &n, &k);
for(int i = 1, la, lb, lc; i <= n - 1; ++i) {
scanf("%d %d %d", &la, &lb, &lc);
adde(la, lb, lc);
adde(lb, la, lc);
}
memset(f, 0xfe, sizeof f);//设为负无穷
//因为可能有负权值
dfs(1, 0, 0);
printf("%lld", f[1][k]);
return 0;
}