1. #include <iostream>
    2. #include <vector>
    3. using namespace std;
    4. class Solver {
    5. static const int MAXN = 100 + 5;
    6. struct Edge {
    7. int idx;
    8. int v, w;
    9. int last;
    10. } edges[MAXN];
    11. int dp[MAXN][MAXN] = {};
    12. int cnt = 0, head[MAXN];
    13. int N, V;
    14. int root = 0;
    15. void addEdge(int v, int w, int fa, int idx) {
    16. if (fa == -1) fa = 0;
    17. edges[cnt] = {idx, v, w, head[fa]};
    18. head[fa] = cnt++;
    19. }
    20. void dfs(int idx) {
    21. for (int e = head[idx]; e != -1; e = edges[e].last) {
    22. Edge edge = edges[e];
    23. dfs(edge.idx);
    24. for (int i = V; i >= edge.v; i--) dp[edge.idx][i] = dp[edge.idx][i - edge.v] + edge.w;
    25. for (int i = 0; i < edge.v; i++) dp[edge.idx][i] = 0;
    26. for (int i = V; i >= 0; i--)
    27. for (int j = i; j >= 0; j--)
    28. dp[idx][i] = max(dp[idx][i], dp[idx][i - j] + dp[edge.idx][j]);
    29. }
    30. }
    31. public:
    32. Solver() {
    33. cin >> N >> V;
    34. for (int i = 0; i <= N; i++) head[i] = -1;
    35. int v[MAXN], w[MAXN], fa[MAXN];
    36. for (int i = 1; i <= N; i++) {
    37. cin >> v[i] >> w[i] >> fa[i];
    38. if (fa[i] == -1) root = i;
    39. }
    40. for (int i = 1; i <= N; i++) addEdge(v[i], w[i], fa[i], i);
    41. // for (int i = 0; i <= N; i++) {
    42. // cout << "root: " << i << endl;
    43. // for (int e = head[i]; e != -1; e = edges[e].last) {
    44. // cout << edges[e].v << " " << edges[e].w << endl;
    45. // }
    46. // cout << "------------" << endl;
    47. // }
    48. dfs(0);
    49. cout << dp[0][V];
    50. }
    51. };
    52. // 大概思路,看成分组背包问题,dp[i][j] 表示以 i 为根的子树,体积为 j 时的最大价值
    53. // 选的时候,可以把子节点所有的 j 看成一组,N 组里面,组内互斥
    54. /*
    55. 建好树以后,从根节点开始,递归算
    56. 每次先算完子节点的所有体积对应的最大价值,然后把每个子节点(直接子节点)看成一组,变成分组背包
    57. 注意一点,某一个节点选完以后,自己也要选,而且选不了自己的状态没有意义,要变 0
    58. */
    59. class Solver2 {
    60. static const int MAXN= 100 + 5;
    61. int v[MAXN], w[MAXN];
    62. vector<int> g[MAXN];
    63. int N, V;
    64. int dp[MAXN][MAXN] = {};
    65. // 更好理解的一种转移
    66. // 一开始,默认所有 [v[x], V] 是选了 w[x]的
    67. // 之后算完子节点后,进行枚举时,j 应该在 [V, v[x]] 里,因为 小于 v[x] 的 j 非法
    68. // 而枚举组内物品时,组内物品体积需要在 [0, j - v[x]] 里,如果超出,则说明不能选 v[x],不合法
    69. void dfs(int idx) {
    70. for (int i = v[idx]; i <= V; i++) dp[idx][i] = w[idx];
    71. for (auto sub : g[idx]) {
    72. dfs(sub);
    73. for (int j = V; j >= v[idx]; j--)
    74. for (int k = 0; k <= j - v[idx]; k++)
    75. dp[idx][j] = max(dp[idx][j], dp[idx][j - k] + dp[sub][k]);
    76. }
    77. }
    78. // 原版转移方式,子节点算完以后,在 [V - v[x], 0] 进行枚举,因为在这范围内枚举,才是能选 子节点的状态,而 [v[x], V] 会在子节点选完后,最后选
    79. // 每个节点算完以后,因为只是转移完,还没有把自己加上去,所以得在最后加上根节点
    80. // 加上根节点时,为什么从大到小,从小到大会使用已经加过根节点的状态
    81. // 为什么要把 [0, v[x] - 1] 赋成 0,不这样做会导致从非法状态转移
    82. void dfs2(int x) {
    83. for (auto y : g[x]) {
    84. dfs(y);
    85. for (int j = V - v[x]; j >= 0; j--)
    86. for (int k = j; k >= 0; k--)
    87. dp[x][j] = max(dp[x][j], dp[x][j - k] + dp[y][k]);
    88. }
    89. for (int i = V; i >= v[x]; i--) dp[x][i] = dp[x][i - v[x]] + w[x];
    90. for (int i = 0; i < v[x]; i++) dp[x][i] = 0;
    91. }
    92. public:
    93. void f() {
    94. cin >> N >> V;
    95. int root, fa;
    96. for (int i = 1; i <= N; i++) {
    97. cin >> v[i] >> w[i] >> fa;
    98. if (fa == -1) root = i;
    99. else g[fa].push_back(i);
    100. }
    101. dfs2(root);
    102. cout << dp[root][V] << endl;
    103. for (int i = 0; i <= N; i++) {
    104. for (int j = 0; j <= V; j++)
    105. cout << dp[i][j] << " ";
    106. cout << endl;
    107. }
    108. }
    109. };
    110. int main() {
    111. Solver2 s;
    112. s.f();
    113. }