P1273 有线电视网(树形dp 分组背包)

原题: https://www.luogu.org/problemnew/show/P1273

题意:

一棵树,节点1为根,边权为花费。到达一个叶子节点会得一分,并且还给你花费 f e e i fee_i 。问不花钱可以得到的最高分。

解析:

首先是树形dp,第二维因为得分和金额中,得分在值上比较小,所以dp[p][v]表示到点p(从下到上)得到v分的最大剩余金额。

显然,到每一个点,对于每个v都有一个值。那么从很多个儿子更新到父亲,这个dp怎么维护呢?

其实很简单,因为一个儿子对得分的贡献只有一种情况,所以对一个儿子的所有状态,做一遍分组背包,最后就维护好父亲的状态了。

分组包的循环顺序:组之间、包大小从大到小、组内,这样保证每一次更新不会从同组或自己的状态转移过来。

#include<bits/stdc++.h>
using namespace std;
#define pill pair<int,int>

vector<pill>V[3001];
int fee[3001];

int dp[3001][3001], Max[3001];
// 记录每个点的最大分数,优化时间
int tmp[3001];

void dfs(int p) {
    if(V[p].empty()) {
        dp[p][0] = 0;
        dp[p][1] = fee[p];
        Max[p] = 1;
        return;
    }
    int maxs = 0;
    dp[p][0] = 0;
    for(int i = 0; i < V[p].size(); i++) {
        int u = V[p][i].first, cost = V[p][i].second;
        dfs(u);
        for(int j = maxs; j >= 0; j--) {
            for(int k = Max[u]; k >= 1; k--) {
                dp[p][j + k] = max(dp[p][j + k], dp[p][j] + dp[u][k] - cost);
            }
        }
        maxs += Max[u];
    }
    Max[p] = maxs;
}

int main() {
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= n; j++) {
            dp[i][j] = -1e9;
        }
    }
    for(int i = 1; i <= n - m; i++) {
        int k;
        scanf("%d", &k);
        while(k--) {
            int c, v;
            scanf("%d%d", &c, &v);
            V[i].push_back({c, v});
        }
    }
    for(int i = 1; i <= m; i++)
        scanf("%d", fee + (n - m + i));
    dfs(1);
    for(int i = Max[1]; i >= 0; i--) {
        if(dp[1][i] >= 0)
            return 0 * printf("%d\n", i);
    }
}

猜你喜欢

转载自blog.csdn.net/jk_chen_acmer/article/details/86741923