P3177 [HAOI2015]树上染色(树上dp)
题目描述
有一棵点数为 n 的树,树边有边权。给你一个在0∼n 之内的正整数 kk ,你要在这棵树中选择 kk 个点,将其染成黑色,并将其他 的 n−k 个点染成白色。将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间的距离的和的受益。问受益最大值是多少。
输入格式
第一行包含两个整数 n,k。
第二到 n 行每行三个正整数 fr, to, dis,表示该树中存在一条长度为 dis 的边 (fr, to)。输入保证所有点之间是联通的。
输出格式
输出一个正整数,表示收益的最大值。
输入输出样例
输入 #1
3 1 1 2 1 1 3 2
输出 #1
3
说明/提示
对于 100\%100% 的数据,0 \leq n,k \leq 20000≤n,k≤2000。
思路:dp的思路很好想,当前节点i选择j个黑色子节点的最大取值
但是我实在是搞不明白咋dp,看了题解才知道,我们可以考虑一条边
的通过次数,假设总黑色节点个数为m,子节点的黑色节点个数为a,则该
边能提供的价值为(m-a)*a*w,这样思路就比较明朗了
对子节点选择黑色节点个数进行背包,就能求出最大值,但是细节比较多
比如初始化一定要全部设置为-1,到达每个节点的时候设置dp[u][0]=dp[u][1]=0
表示以u为根的树拥有0/1个黑色节点提供的价值为0,dp的时候如果用来转移的
dp[u][i-j]还没有值,则不能用来dp
AC代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 2005; struct edge { int t, nxt,dis; }e[maxn<<1]; int hd[maxn], tot; void add(int f, int t,int dis) { e[++tot] = { t,hd[f],dis}; hd[f] = tot; } int n, s1,s2; ll dp[maxn][maxn]; int siz[maxn]; void dfs(int u, int f) { siz[u] = 1; dp[u][0] = dp[u][1] = 0; for (int i = hd[u]; i; i = e[i].nxt) { int v = e[i].t;ll w = e[i].dis; if (v == f)continue; dfs(v, u);siz[u] += siz[v]; for (int j = min(s1,siz[u]); j >= 0; j--) { for (int k =0;k<=min(siz[v], j); k++) { if (dp[u][j - k] == -1)continue;//用来转移的位置还没有解 ll val = w * ((k * (s1 - k) + (s2 - siz[v] + k) * (siz[v] - k))); dp[u][j] = max(dp[u][j], dp[u][j - k] + dp[v][k] + val); } } } } int main() { //freopen("test.txt", "r", stdin); scanf("%d%d", &n, &s1); memset(dp, -1, sizeof(dp)); s2 = n - s1; if (s1 > s2)swap(s1, s2);//黑白是可以颠倒的,加快求解 for (int i = 1; i < n; i++) { int a, b, c; scanf("%d%d%d", &a, &b, &c); add(a, b, c); add(b, a, c); } dfs(1, 0); cout << dp[1][s1] << endl; return 0; }