树形背包——是背包的一种,它的特征是物品之间会有依赖关系,必须要先选一个物品,再选一个物品。
如果选择一个物品,则必须选择它的父节点。
第 i 件物品的体积为v[i] ,价值为 w[i],其父节点物品编号为p[i] ,其中根节点p[i] = -1 。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
在这个之前,我们要有一些基本知识;
链式向前星存图:
它的作用就是可以快速找到一个节点的所有子节点,与我们之前拿二维数组存储,优点是内存小,且速度也要快。
void add(int a, int b) {
a为父节点,b为子节点。
e[idx] = b;
e 用来存取子节点
ne[idx] = he[a];
ne 用来存取上一个a的子节点的的idx,以方便查找
he[a] = idx;
存取现在的子节点
idx++;
}
对于这些基础的数据结构,我们一般是想不到的,所有第一任务还是理解,然后把他们背下来。
就是一看就会,一写不会。
补充完前提条件后,我们可以去理理思路了。
首先因为选字节点之前一定要选父节点,我们就可以从下而上的进行dfs,先找出子节点的最好情况,然后进而推出父节点。
// 遍历节点 u 的所有子节点 x(分组背包遍历物品组)
for (int i = he[u]; i != -1; i = ne[i]) {
int x = e[i];
// 递归处理节点 x
dfs(x);
// 从大到小遍历背包容量(分组背包遍历容量)
for (int j = c; j >= 0; j--) {
// 遍历给节点 x 分配多少背包容量(分组背包遍历决策)
for (int k = 0; k <= j - cv; k++) {
f[u][j] = Math.max(f[u][j], f[u][j - k] + f[x][k]);
}
}
}
接下不是全部代码:
class Solution {
int N = 110, M = N * 2;
int[] he = new int[N], e = new int[M], ne = new int[M];
int[] vi, wi;
int n, c, idx;
// 定义 f[u][j] 为考虑以 u 为根的子树,背包容量不超过 j 的最大价值
int[][] f = new int[N][N];
// 链式向前星存图
void add(int a, int b) {
e[idx] = b;
ne[idx] = he[a];
he[a] = idx;
idx++;
}
void dfs(int u) {
// 节点 u 的价值和体积
int cw = wi[u], cv = vi[u];
// 要选任一节点,必须先选 u,同时也限制了至少需要 cv 的容量
for (int i = cv; i <= c; i++) f[u][i] += cw;
// 遍历节点 u 的所有子节点 x(分组背包遍历物品组)
for (int i = he[u]; i != -1; i = ne[i]) {
int x = e[i];
// 递归处理节点 x
dfs(x);
// 从大到小遍历背包容量(分组背包遍历容量)
for (int j = c; j >= 0; j--) {
// 遍历给节点 x 分配多少背包容量(分组背包遍历决策)
for (int k = 0; k <= j - cv; k++) {
f[u][j] = Math.max(f[u][j], f[u][j - k] + f[x][k]);
}
}
}
}
public int maxValue(int N, int C, int[] p, int[] v, int[] w) {
n = N; c = C;
vi = v; wi = w;
Arrays.fill(he, -1);
int root = -1;
for (int i = 0; i < n; i++) {
if (p[i] == -1) {
root = i;
} else {
add(p[i], i);
}
}
dfs(root);
return f[root][c];
}
}
感谢三叶姐。