P3177 [HAOI2015]树上染色(树上dp)

题目描述

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

输入格式

第一行包含两个整数 n,k。

第二到 n 行每行三个正整数 fr, to, dis,表示该树中存在一条长度为 dis 的边 (fr, to)。输入保证所有点之间是联通的。

输出格式

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

输入输出样例

输入 #1
3 1
1 2 1
1 3 2
输出 #1
3

说明/提示

对于 100\%100% 的数据,0 \leq n,k \leq 20000n,k2000。

思路:dp的思路很好想,当前节点i选择j个黑色子节点的最大取值

但是我实在是搞不明白咋dp,看了题解才知道,我们可以考虑一条边

的通过次数,假设总黑色节点个数为m,子节点的黑色节点个数为a,则该

边能提供的价值为(m-a)*a*w,这样思路就比较明朗了

对子节点选择黑色节点个数进行背包,就能求出最大值,但是细节比较多

比如初始化一定要全部设置为-1,到达每个节点的时候设置dp[u][0]=dp[u][1]=0

表示以u为根的树拥有0/1个黑色节点提供的价值为0,dp的时候如果用来转移的

dp[u][i-j]还没有值,则不能用来dp

AC代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2005;
struct edge {
    int t, nxt,dis;
}e[maxn<<1];
int hd[maxn], tot;
void add(int f, int t,int dis) {
    e[++tot] = { t,hd[f],dis};
    hd[f] = tot;
}
int n, s1,s2;
ll dp[maxn][maxn];
int siz[maxn];
void dfs(int u, int f) {
    siz[u] = 1; dp[u][0] = dp[u][1] = 0;
    for (int i = hd[u]; i; i = e[i].nxt) {
        int v = e[i].t;ll w = e[i].dis;
        if (v == f)continue;
        dfs(v, u);siz[u] += siz[v];
        for (int j = min(s1,siz[u]); j >= 0; j--) {
            for (int k =0;k<=min(siz[v], j); k++) {
                if (dp[u][j - k] == -1)continue;//用来转移的位置还没有解
                    ll val = w * ((k * (s1 - k) + (s2 - siz[v] + k) * (siz[v] - k)));
                    dp[u][j] = max(dp[u][j], dp[u][j - k] + dp[v][k] + val);
            }
        }
    }
}
int main() {
    //freopen("test.txt", "r", stdin);
    scanf("%d%d", &n, &s1);
    memset(dp, -1, sizeof(dp));
    s2 = n - s1;
    if (s1 > s2)swap(s1, s2);//黑白是可以颠倒的,加快求解
    for (int i = 1; i < n; i++) {
        int a, b, c; scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
        add(b, a, c);
    }
    dfs(1, 0);
    cout << dp[1][s1] << endl;
    return 0;
}

 

 

posted @ 2021-03-23 19:54  cono奇犽哒  阅读(50)  评论(0)    收藏  举报