#include using namespace std; const int N = 110, M = N << 1; int v[N], w[N]; int n, m, root; // f[u][i][j] 以u为根,在前i个子树中(组)选择,最大体积不超过j的所有方案 // 属性:max价值 int f[N][N][N]; // 链式前向星 int e[M], h[N], idx, ne[M]; void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; } // u:以u为根的树 // 返回值:以u为根的树,一级子树son的个数 int dfs(int u) { /* ① 以u为根的树,如果现在剩余体积j>=v[u], 最起码可以把u装下,能获取到w[u]的价值 如果你想研究一下以u为根的子树最多可以获得多大价值,那u节点是必须装下来,否则后续子树根本没机会讨论 */ for (int j = v[u]; j <= m; j++) f[u][0][j] = w[u]; // ② 考虑u的每个子树 int s = 0; for (int i = h[u]; ~i; i = ne[i]) { // 枚举u的每个子节点 s++; // 前s个子树 int son = e[i]; // 在链式前向星中,这是几号节点 int c = dfs( son); // 对子节点son,把它的f[son][c][k]信息填充完整,返回son子树的一级结点个数 /* 目标:填充f[u][i][j], i: 1~dfs(u)的每个值,dfs(u)返回的是u的下级节点个数,也就是一级子树个数,0上面讨论过了,不用再讨论 j: 枚举每一个u子树的合法空间j k:给定j这么大的空间,那么为son这棵子树留多大空间,k∈ [0,j-v[u]],son子树可以贡献的最大价值依定义就是f[son][c][k] f[u][s - 1][j - k]:在处理s个子树前,前s-1个都已经填充完毕,可以依赖。由于son占了k个空间,那么前序依赖就是f[u][s-1][j-k] */ for (int j = v[u]; j <= m; j++) for (int k = 0; k <= j - v[u]; k++) f[u][s][j] = max(f[u][s][j], f[u][s - 1][j - k] + f[son][c][k]); } return s; // 返回u有多少个子结点 } int main() { // 初始化链式前向星 memset(h, -1, sizeof h); cin >> n >> m; // 物品个数和背包容量 for (int i = 1; i <= n; i++) { // n个物品 int p; cin >> v[i] >> w[i] >> p; // 体积、价值、父亲 if (p == -1) root = i; // 对一棵树而言,根是最重要的 else add(p, i); // 从父亲向儿子建边,一般树都是从上到下建边 } int s = dfs(root); // 从根节点开始遍历,填充DP Table,返回值是root有几个子树 /* Q:为什么需要dfs返回root有多少个子树呢? A:DP问题,一般的答案都在状态转移方程上。比如f[u][i][j]表示以u为根的树中,在前i个儿子中去找,在体积不超过j的情况下,最大的价值是多少。 我们可以看出,最终的答案就是f[root][root的儿子总数][m] 这样看来,计算返回出root的儿子总数就是合情合理了 */ printf("%d\n", f[root][s][m]); return 0; }