AcWing
10. 有依赖的背包问题
树形DP
+分组背包
。定义f[uu][m]
表示以uu
为根节点,容量为m
的(子)树的最大价值。对于树上的某个节点,其最大的可能价值是节点本身的价值
+选择取到的子树对应的价值之和
。将节点的若干个子树看作若干个分组,可以选择取子树,也可以不取子树。将分组内按照所有可能的体积k划分,f[vv][k]
表示以vv
为根节点的子树,体积为k
时的最大价值。
状态转移方程为:
f[uu][j] = max{f[uu][j], f[uu][j-k] + f[vv][k]}, for a certain k.
考虑边界条件,背包初始的价值,即未选择任何一个分组时的价值,f[uu][m] = w[uu], m >= v[uu]
. 此处,uu
代表当前所有分组外的公共父节点,它仅用于树上操作。所以dp
数组其实是省略了考虑前n
个分组,这个维度的,完整的dp
数组应该是f[uu][n][m]
.
所以对于每个确定的子树tr
,其有三层循环,最外层枚举其根节点下面的所有子树,表示所有分组,第二层枚举容量(体积)j
,容量应该从v[uu]
枚举到m
,确保一定包括了根节点,否则树tr
的价值为0
,第三层枚举分组内部的决策,具体地说,将每个分组划分为所有可能的体积(0
到j-v[uu]
,因为要排除掉背包初始占据的体积),通过这些所有可能的体积对应的最大价值,选出组内最优的决策。
通过dfs
,自底向上地递归出问题的解。每次迭代都是一次单独的分组背包问题的求解,其以f
的第一维uu
作为标识。
#include <bits/stdc++.h>
using namespace std;
int n, m;
const int N = 110, M = 110;
int v[N], w[N];
int h[N], e[N], ne[N], idx;
int f[N][M];
void add(int a, int b){
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dfs(int uu){
for(int j = v[uu]; j <= m; j++) f[uu][j] = w[uu];
for(int i = h[uu]; ~i; i = ne[i]){
int vv = e[i];
dfs(vv);
for(int j = m; j >= v[uu]; j--){
for(int k = 0; k <= j - v[uu]; k++){
f[uu][j] = max(f[uu][j], f[uu][j-k] + f[vv][k]);
}
}
}
}
int main(){
cin >> n >> m;
memset(h, -1, sizeof h);
int root;
for(int i = 1; i <= n; i++){
int p;
cin >> v[i] >> w[i] >> p;
if(p != -1) add(p, i);
else root = i;
}
dfs(root);
cout << f[root][m] << endl;
return 0;
}