【BZOJ4033】【HAOI2015】树上染色 题解

4033: [HAOI2015]树上染色


Time Limit: 10 Sec Memory Limit: 256 MB

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 为根的子树中有j个黑点的最大收益。

       可是仔细想想,转移并不是那么容易,如果直接计算每个点到相同颜色的点的距离和是不可行的。这时,我们可以换个角度思考,不妨考虑每条边对答案的贡献,而贡献是很好求的,长度为 z z 连接x,y两点的边对答案的贡献即为

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;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值