bzoj4033
4033: [HAOI2015]树上染色
Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 1598 Solved: 667
[Submit][Status][Discuss]
Description
有一棵点数为N的树,树边有边权。给你一个在0~N之内的正整数K,你要在这棵树中选择K个点,将其染成黑色,并
将其他的N-K个点染成白色。将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间距离的和的收益。
问收益最大值是多少。
Input
第一行两个整数N,K。
接下来N-1行每行三个正整数fr,to,dis,表示该树中存在一条长度为dis的边(fr,to)。
输入保证所有点之间是联通的。
N<=2000,0<=K<=N
Output
输出一个正整数,表示收益的最大值。
Sample Input
5 2
1 2 3
1 5 1
2 3 1
2 4 2
1 2 3
1 5 1
2 3 1
2 4 2
Sample Output
17
【样例解释】
将点1,2染黑就能获得最大收益。
【样例解释】
将点1,2染黑就能获得最大收益。
HINT
Source
这道题搞了很长时间(弱爆了)
设计dp状态的时候有一个很烦人的问题,就是怎么考虑树外的点的贡献。(贡献自己脑补是什么)
碰见这种题目,我们应该这么想:既然算不了树外的,那么我们把树内能算的东西都算了。
原始思想:dp(i,j)以i为根子树内的答案的最大值,但是一旦我们想转移就gg了,于是我们把子树内的点的贡献也算上。
怎么算呢?程序里写了,w是i的父亲到i的权值,我们把子树里所有点经过这条边的贡献算上就行了。
temp每次都要清空,因为每次计算一个新的子树,答案要清空 temp保存当前这颗子树选了j个黑点的自己的最大贡献,最后再加上向外面的贡献
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 2010; struct edge { int nxt, to, w; } e[N << 1]; int n, m, cnt = 1; int head[N], size[N]; ll dp[N][N], temp[N]; void link(int u, int v, int w) { e[++cnt].nxt = head[u]; head[u] = cnt; e[cnt].to = v; e[cnt].w = w; } void dfs(int u, int last, int w) { size[u] = 1; memset(temp, 0, sizeof(temp)); for(int i = head[u]; i ; i = e[i].nxt) if(e[i].to != last) { dfs(e[i].to, u, e[i].w); for(int j = 0; j <= size[u]; ++j) temp[j] = dp[u][j]; for(int j = 0; j <= min(size[e[i].to], m); ++j) for(int k = 0; k <= min(size[u], m); ++k) temp[j + k] = max(temp[j + k], dp[u][k] + dp[e[i].to][j]); size[u] += size[e[i].to]; for(int j = 0; j <= size[u]; ++j) dp[u][j] = max(temp[j], dp[u][j]); } for(int j = 0; j <= min(size[u], m); ++j) dp[u][j] = temp[j] + ((ll)j*(ll)(m-j)+(ll)(size[u]-j)*(ll)(n-m-size[u]+j))*(ll)w; } int main() { scanf("%d%d", &n, &m); for(int i = 1; i < n; ++i) { int u, v, w; scanf("%d%d%d", &u, &v, &w); link(u, v, w); link(v, u, w); } dfs(1, 0, 0); printf("%lld\n", dp[1][m]); return 0; }