[HAOI2015] 树上染色

[HAOI2015] 树上染色

题目大意:有一棵点数为\(N\)的树,树边有边权,你要在这棵树中选择 \(K\)个点,将其染成黑色,并将其他的\(N-K\)个点染成白色,将所有点染色后,会获得黑点两两之间的距离加上白点两两之间的距离的和的受益.问受益最大值是多少.$ 0\leq K\leq N \leq2000$

Solution

普通的定义\(f[i][j]\)为以\(i\)为根,选取\(j\)个黑点,所得的最大收益是不行的,不宜于转移

我们考虑下面一种定义

  • 状态:\(f[i][j]\)为以\(i\)为根,选取\(j\)个黑点的总贡献

这就有点抽象了,我们打个比方,假设要选\(1\)个黑点,树的形态是三个点的链,我们现在到了叶子节点,那么这个结点被选为黑点所产出的贡献是节点内的白子数量(0)乘上节点外的白子数量(2),加上结点内的黑子数量(1)乘上结点外的黑子数量(0),再乘上此叶子结点连到父亲的边权,贡献是0,因为当此节点被选为黑点,黑白双方都不可能经过这个结点

Code

#include <bits/stdc++.h>

using namespace std;

const int N = 2005;

int ecnt, n, k;
int head[N], siz[N];
long long f[N][N];

struct Edge {
    int to, dis, val, next;
}e[N << 1];

inline void adde(int x, int y, int z) {
    e[++ecnt].to = y;
    e[ecnt].next = head[x];
    e[ecnt].val = z;
    head[x] = ecnt; 
}

void dfs(int x, int y, int z) {
    f[x][0] = f[x][1] = 0, siz[x] = 1;
    for(int i = head[x]; i; i = e[i].next) {
        if(e[i].to == y) continue;
        dfs(e[i].to, x, e[i].val);
        for(int j = min(siz[x], k); j >= 0; --j)
            for(int m = min(siz[e[i].to], k - j); m >= 0; --m) {
                f[x][j + m] = max(f[x][j + m], f[e[i].to][m] + f[x][j]);
            }
        siz[x] += siz[e[i].to];
    }
    for(int j = 0; j <= min(siz[x], k); ++j) {
        f[x][j] += 1LL * z * (j * (k - j) + (siz[x] - j) * (n - k - siz[x] + j));//加上到父节点的边的贡献
    }
        
}

inline int read() {
    int x = 0, f = 1;
    char c = getchar();
    while (c < '0' || c > '9') {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') {
        x = (x << 3) + (x << 1) + (c ^ 48);
        c = getchar();
    }
    return x * f;
}

int main() {
    scanf("%d %d", &n, &k);
    for(int i = 1, la, lb, lc; i <= n - 1; ++i) {
        scanf("%d %d %d", &la, &lb, &lc);
        adde(la, lb, lc);
        adde(lb, la, lc);
    }
    memset(f, 0xfe, sizeof f);//设为负无穷
    //因为可能有负权值 
    dfs(1, 0, 0);
    printf("%lld", f[1][k]);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/LMSH7/p/9711127.html