[BZOJ4033]:[HAOI2015]树上染色(树上DP)
题目传送门
题目描述
有一棵点数为N的树,树边有边权。给你一个在0~N之内的正整数K,你要在这棵树中选择K个点,将其染成黑色,并将其他的N-K个点染成白色。将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间距离的和的收益。
问收益最大值是多少。
输入格式
第一行两个整数N,K。
接下来N-1行每行三个正整数fr,to,dis,表示该树中存在一条长度为dis的边(fr,to)。
输入保证所有点之间是联通的。
N<=2000,0<=K<=N。
输出格式
输出一个正整数,表示收益的最大值。
样例
样例输入:
5 2
1 2 3
1 5 1
2 3 1
2 4 2
样例输出:
17
数据范围与提示
样例解释:将点1,2染黑就能获得最大收益。
N≤2000, 0≤K≤N。
题解
看提第一眼,树上DP。
定义dp[i][j]表示以i为根节点的子树上有j个黑点的最大收益。
那么,显然,这条边对答案的贡献只与它的子树内、外有几个黑点、白点有关,与它的子树的子树无关。
设点x的子树和为size[x],如果它的子树里有black个黑点,那么它的子树中就有size[x]-black个白点,它的子树外就有k-black个黑点,n-k-(size[x]-black)个白点,那么,这条边对答案的贡献就为{black×(size[x]-black)+(k-black)×[n-k-(size[x]-black)]}×边权。
代码时刻
#include<bits/stdc++.h> using namespace std; struct rec { int nxt; int to; int w; }e[4000]; int head[2001],cnt; int n,b; long long dp[2001][2001],flag[2001];//记得开long long…… int size[2001]; void add(int x,int y,int w) { e[++cnt].nxt=head[x]; e[cnt].to=y; e[cnt].w=w; head[x]=cnt; } void dfs(int x)//树上DP { size[x]=1; for(int i=head[x];i;i=e[i].nxt) { if(size[e[i].to])continue; dfs(e[i].to); memset(flag,0,sizeof(flag)); for(int j=0;j<=min(b,size[x]);j++) for(int k=0;k<=min(b,size[e[i].to])&&j+k<=b;k++) flag[j+k]=max(flag[j+k],dp[x][j]+dp[e[i].to][k]+(k*(b-k)+1LL*(size[e[i].to]-k)*(n-b-size[e[i].to]+k))*e[i].w);//转移 for(int j=0;j<=b;j++)dp[x][j]=flag[j]; size[x]+=size[e[i].to]; } } int main() { scanf("%d%d",&n,&b); for(int i=1;i<n;i++) { int fr,to,dis; scanf("%d%d%d",&fr,&to,&dis); add(fr,to,dis); add(to,fr,dis); } dfs(1); cout<<dp[1][b]<<endl; return 0; }
rp++