P3177 [HAOI2015]树上染色(树形DP)
题目链接:https://www.luogu.org/problem/P3177
题目描述
有一棵点数为 N 的树,树边有边权。给你一个在 0~ N 之内的正整数 K ,你要在这棵树中选择 K个点,将其染成黑色,并将其他 的N-K个点染成白色 。 将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间的距离的和的受益。问受益最大值是多少。
输入格式
第一行包含两个整数 N, K 。接下来 N-1 行每行三个正整数 fr, to, dis , 表示该树中存在一条长度为 dis 的边 (fr, to) 。输入保证所有点之间是联通的。
输出格式
输出一个正整数,表示收益的最大值。
输入输出样例
输入 #1
3 1 1 2 1 1 3 2
输出 #1
3
说明/提示
对于 100% 的数据, 0<=K<=N <=2000
解题思路:首先设状态dp【i】【j】为在i的子树下涂了j个黑点的最大价值,然后我们可以算当前边对答案的贡献了,贡献w=边长*边的一边黑点的数量*边的另一边黑点的数量+边长*边的一边白点的数量*边的另一边白点的数量,然后就可以进行状态转移了。值得注意的是,转移是需要判断涂点的不合法的情况,比如说我们枚举在当前的子节点的子树中,我们枚举它里面有1个黑点,那么我们需要一个在其他子树中选了共 j - 1 个黑点的状态,但是如果其他的子树的大小总和还不到 j - 1 的话,那么这个状态显然是不合法的,所以我们要去除这种情况。(一开始没想到这个WA哭了。。QAQ)
#include<bits/stdc++.h> using namespace std; const int maxn=2e3+10; typedef long long ll; ll dp[maxn][maxn]; int n,k; struct st{ int to,next; ll val; }stm[maxn*2]; int cnt; int head[maxn]; int sum[maxn]; void add(int u,int v,ll val){ stm[cnt].to=v; stm[cnt].next=head[u]; stm[cnt].val=val; head[u]=cnt++; } void dfs(int now,int fa){ sum[now]=1; for(int i=head[now];~i;i=stm[i].next){ int to=stm[i].to; if(to==fa)continue; dfs(to,now); sum[now]+=sum[to]; } } void dfs1(int now,int fa){ dp[now][0]=dp[now][1]=0;//这两个状态肯定合法 for(int i=head[now];~i;i=stm[i].next){ int to=stm[i].to; ll val=stm[i].val; if(to==fa){ continue; } dfs1(to,now); for(int j=min(sum[now],k);j>=0;j--){ for(int m=0;m<=j&&m<=sum[to];m++){ if(dp[now][j-m]==-1)continue;//判断当前状态转移是否合法 dp[now][j]=max(dp[now][j],dp[now][j-m]+dp[to][m]+val*m*(k-m)+val*(sum[to]-m)*(n-k-sum[to]+m)); } } } } int main(){ int u,v; ll val; cnt=0; memset(head,-1,sizeof(head)); memset(dp,-1,sizeof(dp)); scanf("%d%d",&n,&k); for(int i=1;i<n;i++){ scanf("%d%d%lld",&u,&v,&val); add(u,v,val); add(v,u,val); } dfs(1,-1); dfs1(1,-1); printf("%lld\n",dp[1][k]); return 0; }