[BZOJ4033]:[HAOI2015]树上染色(树上DP)

题目传送门


题目描述

有一棵点数为N的树,树边有边权。给你一个在0~N之内的正整数K,你要在这棵树中选择K个点,将其染成黑色,并将其他的N-K个点染成白色。将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间距离的和的收益。
问收益最大值是多少。


输入格式

第一行两个整数NK
接下来N-1行每行三个正整数frtodis,表示该树中存在一条长度为dis的边(fr,to)
输入保证所有点之间是联通的。
N<=20000<=K<=N


输出格式

输出一个正整数,表示收益的最大值。


样例

样例输入:

5 2
1 2 3
1 5 1
2 3 1
2 4 2

样例输出:

17


数据范围与提示

样例解释:将点12染黑就能获得最大收益。

N≤20000≤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++

posted @ 2019-07-15 19:58  HEOI-动动  阅读(158)  评论(0编辑  收藏  举报