题目描述
有一棵点数为n的树,树边有边权。将m个点染成黑色,并将其他的点染成白色。会获得黑点两两之间的距离和加上白点两两之间的距离和的收益。问收益最大值是多少。
输入输出格式
输入格式:
第一行两个整数n、m。接下来n-1行,每行三个整数a、b、c,表示有一条树边连接a、b,长度为c。
输出格式:
一行 一个正整数,表示收益的最大值。
题解
由题目我们可以考虑动态规划,令f[i][j]表示以i为根节点的子树上有j个结点为黑色可以得到的最大贡献。这就成了一个树上背包问题了。
对于f[i][j]这个状态,除i以外的所有点可以被分为两类,一类是在i 的子树上的,另一类是在i的子树外的。而在这两个集合中各取一个点相连一定会经过边(i, father[i])我们可以算出在此时(i,father[i])产生的收益是(设子树大小为size[i])子树上的黑点个数(j)与子树外的黑点个数(m - j)的乘积乘上这条边的边权(w[i])加上子树上白点的个数(size[i] - j)乘以子树外白点的个数(n - m - size[i] + j)再乘以边权,这些贡献是在加入了根节点以后才产生的新的贡献,与子树上黑白点如何分配无关。
然后,我们再来考虑子树上黑白点的分配方法,由于之前的子树分配对后面的子树如何分配没有影响,我们可以把子树分为两类,一类是已经确定大小的,一类是还没有确定大小的,而这其中产生的贡献即为当前讨论的子树上的黑点个数可以带来的贡献值,和这颗子树以外的点可以带来的贡献值,而这颗子树以外的点可以带来的贡献值,我们就只能先计算那些我们已经划分好了的,而其余的我们可以留在后面的子树再继续划分(这是我觉得这道题最玄学的地方),所以我们状态的转移要像这样写:
void Dp(int u) { size[u] = 1; for(int i = Head[u]; i != -1; i = Next[i]) { int v = To[i]; if(size[v] != 0) continue; Dp(v); memset(g, 0, sizeof(g)); for(int t = 0; t <= min(m, size[u]); t ++) for(int j = 0; j <= min(m, size[v]) && j +t <= m; j ++) g[t + j] = max(g[t + j],f[u][t] + f[v][j] + (long long) (j * (m - j) + (size[v] - j) * (n - m - (size[v] - j))) * w[i]); for(int j = 0; j <= m; ++ j) f[u][j] = g[j]; size[u] += size[v]; } }
代码
#includeusing namespace std; const int MAX = 2005; long long f[MAX][MAX], g[MAX]; int Head[MAX], To[MAX << 1], Next[MAX << 1], edgenum = 0, w[MAX << 1]; int n, m; void Add_edge(int from, int to, int c) { Next[++ edgenum] = Head[from]; Head[from] = edgenum; To[edgenum] = to; w[edgenum] = c; } int size[MAX]; void Dp(int u) { size[u] = 1; for(int i = Head[u]; i != -1; i = Next[i]) { int v = To[i]; if(size[v] != 0) continue; Dp(v); memset(g, 0, sizeof(g)); for(int t = 0; t <= min(m, size[u]); t ++) for(int j = 0; j <= min(m, size[v]) && j +t <= m; j ++) g[t + j] = max(g[t + j],f[u][t] + f[v][j] + (long long) (j * (m - j) + (size[v] - j) * (n - m - (size[v] - j))) * w[i]); for(int j = 0; j <= m; ++ j) f[u][j] = g[j]; size[u] += size[v]; } } int main() { memset(Head, -1, sizeof(Head)); int x, y, z; scanf("%d%d", &n, &m); for(int i = 1; i < n; ++ i) { scanf("%d%d%d", &x, &y, &z); Add_edge(x, y, z); Add_edge(y, x, z); } Dp(1); printf("%lld\n", f[1][m]); return 0; }