4033: [HAOI2015]树上染色
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
Sample Output
17
【样例解释】
将点1,2染黑就能获得最大收益。
HINT
2017.9.12新加数据一组 By GXZlegend
Source
鸣谢bhiaibogf提供
这道题一眼看上去就是树形DP,状态很好表示: dp[i][j] d p [ i ] [ j ] 表示以 i i 为根的子树中有个黑点的最大收益。
可是仔细想想,转移并不是那么容易,如果直接计算每个点到相同颜色的点的距离和是不可行的。这时,我们可以换个角度思考,不妨考虑每条边对答案的贡献,而贡献是很好求的,长度为 z z 连接两点的边对答案的贡献即为
z⋅(x一边的白点数量×y一边的白点数量+x一边的黑点数量×y一边的黑点数量)
z
·
(
x
一
边
的
白
点
数
量
×
y
一
边
的
白
点
数
量
+
x
一
边
的
黑
点
数
量
×
y
一
边
的
黑
点
数
量
)
,直接转移即可,加上每次枚举黑点数量只需要枚举到 min(size,k) m i n ( s i z e , k ) ,综合复杂度 O(n O ( n 2 2 。
附上代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<vector>
using namespace std;
long long dp[2010][2010];
bool vis[2010][2010];
int sz[2010];
long long up[2010];
int n,k;
vector<pair<int,long long> >G[2010];
void solve(int x,int p)
{
sz[x]=1;
vis[x][0]=true;
for(int i=0;i<G[x].size();i++)
{
int y=G[x][i].first;
if(y==p)continue;
up[y]=G[x][i].second;
solve(y,x);
sz[x]+=sz[y];
for(int j=min(sz[x],k);j>=0;j--)
{
for(int t=0;t<=min(j,sz[y]);t++)
{
if(!vis[x][j-t])continue;
dp[x][j]=max(dp[x][j],dp[x][j-t]+dp[y][t]);
vis[x][j]=true;
}
}
}
for(int j=min(sz[x],k);j;j--)
{
dp[x][j]=max(dp[x][j],dp[x][j-1]);
}
for(int j=0;j<=min(sz[x],k);j++)
{
dp[x][j]+=1LL*up[x]*(1LL*j*(k-j)+1LL*(sz[x]-j)*(n-k-sz[x]+j));
}
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<n;i++)
{
int x,y;
long long z;
scanf("%d%d%lld",&x,&y,&z);
G[x].push_back(make_pair(y,z));
G[y].push_back(make_pair(x,z));
}
solve(1,0);
printf("%lld",dp[1][k]);
return 0;
}