BZOJ 4033 树上染色 (题解)(树上动规)

最近我总是辗转反侧,难以入眠,对我们曾有过的愿景,浮想联翩。但亲爱的,我早已在内心深处祈祷着,祈祷自己不再迷失于金钱的追逐中。

想了好久终于明白错在哪里了,,,,
题意如下:
有一棵点数为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
这是一道树上动态规划题,怎么设计状态呢。我们定义f[i][j]表示以i为根的子树上有j个黑点的最大收益,那么答案就是f[1][k]。
怎么进行状态转移呢?
这是一个好问题。
如果我们把子树外的点给遍历一遍这样肯定会超时,所以我们曲线救国。
我们如果把点到点的距离微分成几个部分,那么我们在状态转移的时候只用计算当前子树内的所有黑点到父节点的距离乘以子树外的黑点的个数即可,因为这一段不管外面的点的情况如何都会对最终答案产生贡献,而我们不考虑子树内的点互相的距离,因为已经计算过了(这里要细想),而白点的计算同理。
最后提醒:要用long long因为会爆int 。。。
直接上代码:

#include "cstdio"
#include "algorithm"
#include "cstring"
#include "iostream"
using namespace std;
const int N = 2005;
int n, k, x, y, z, num, head[N], siz[N];
long long f[N][N];
struct Edge{
    int v, next, w;
};
Edge e[2 * N];
void adde( int i, int j, int w ) {
    e[++num].v = j;
    e[num].w = w;
    e[num].next = head[i];
    head[i] = num;
}
void dfs( int u, int fa ) {
    siz[u] = 1;
    for ( int i = head[u]; i; i = e[i].next ) {
        int v = e[i].v;
        if ( v == fa ) continue;
        dfs(v, u);
        for ( int  p = siz[u]; p >= 0; p-- ) {//注意反着枚举防止状态被改变多次
            for ( int q = 1; q <= siz[v]; q++ )//想想为什么不从0开始
                f[u][p + q] = max( f[u][p + q], f[u][p] + f[v][q] + (long long)e[i].w*q*(k-q)+ (long long)e[i].w*(n-k-siz[v]+q)*(siz[v]-q) );//计算黑点与白点与外界黑白点的部分距离
            f[u][p] += f[v][0] + (long long)e[i].w*(n-k-siz[v])*siz[v];
        }
        siz[u] += siz[v];//在转移后加siz,想象为什么
    }
}
int main() {
    scanf( "%d%d", &n, &k );
    for ( int i = 1; i < n; i++ ) {
        scanf( "%d%d%d", &x, &y, &z );
        adde( x, y, z );
        adde( y, x, z );
    }
    dfs(1, 0);
    cout << f[1][k] << endl;
    return 0;
}

最后附上我对你的思念。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值