原题: https://www.luogu.org/problemnew/show/P1273
题意:
一棵树,节点1为根,边权为花费。到达一个叶子节点会得一分,并且还给你花费 。问不花钱可以得到的最高分。
解析:
首先是树形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);
}
}