-
问题背景
- 一个容量是V的背包和N组物品,每组物品有s
i个,每件物品的体积是vij,价值是wij,同一组内的物品最多只能选一个,求能装走物品的最大价值
- 一个容量是V的背包和N组物品,每组物品有s
-
Dp分析
-
状态表示
-
二维状态表示
f(i,j) -
f(i,j)表示的是哪一个集合:所有满足如下条件的选法集合- 只从前
i个物品中选 - 选出来的物品的总体积小于等于
j
- 只从前
-
f(i,j)存的是什么属性:Max,Min,数量;在这里f(i,j)存的应该是最大值,即所有满足这种选法的最大值
-
-
状态计算:
f(i,j)可以怎么算出来?- 分组背包问题将集合划分为
s[i]+1个子集- 不含第**s
i**组物品的选法- f(i, j) = f(i - 1, j)
- 含第**s
i**组物品的选法- f(i, j) = max( f(i - 1, j - v
ik) + wik) - 这种选法只有当 j >= v
ik时才有意义
- f(i, j) = max( f(i - 1, j - v
- 不含第**s
- 因此 f(i, j) = max( f(i - 1, j), f(i - 1, j - v
ik) + wik)
- 分组背包问题将集合划分为
-
代码
-
//初始化f[0][0 ~ m]都是0,所以省略 for (int i = 1; i <= n; i++) { for (int j = 0; j <= m; j++) { //不选第i组物品的情况 f[i][j] = f[i - 1][j]; //选第i组物品的情况 for (int k = 1; k <= S[i]; k++) { if (j >= V[i][k]) { f[i][j] = Math.max(f[i][j], f[i - 1][j - V[i][k]] + W[i][k]); } } } }
-
-
-
优化:将二维转换成一维
-
观察状态计算的过程,可以发现两个特点:
- f( i ) 层的计算只用到了f(i - 1) 层
- f( i ) 层中的 j 的计算只用到了f(i - 1) 层中的 j 和 j - v
ik
-
因此可以做如下优化
- 将 i 这一维直接优化掉,i 的每一层循环中的 j 都在原先的 j 上进行覆盖
- j 的每一层循环中要注意 ,在计算 j 时要用到当前上一层 j和 j - v
ik,由于j - vik< j,所以在这一层循环中要从后往前遍历,防止 j - vik被提前更新
-
代码
-
//初始化f[0][0 ~ m]都是0,所以省略 for (int i = 1; i <= n; i++) { for (int j = m; j >= 0; j--) { for (int k = 1; k <= S[i]; k++) { if (j >= V[i][k]) { f[j] = Math.max(f[j], f[j - V[i][k]] + W[i][k]); } } } }
-
-
练习
- 题目
- 题解1
import java.io.*;
import java.util.*;
public class Main {
public static final int N = 110;
public static int[][] V = new int[N][N];
public static int[][] W = new int[N][N];
public static int[] S = new int[N];
//只从前i个物品中选,选出来的物品的总体积小于等于j,满足这两个条件的价值的最大值
public static int[][] f = new int[N][N];
public static int n, m;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
String[] str1 = br.readLine().split(" ");
n = Integer.parseInt(str1[0]);
m = Integer.parseInt(str1[1]);
for (int i = 1; i <= n; i++) {
S[i] = Integer.parseInt(br.readLine());
for (int j = 1; j <= S[i]; j++) {
String[] str2 = br.readLine().split(" ");
V[i][j] = Integer.parseInt(str2[0]);
W[i][j] = Integer.parseInt(str2[1]);
}
}
//初始化f[0][0 ~ m]都是0,所以省略
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {
//不选第i组物品的情况
f[i][j] = f[i - 1][j];
//选第i组物品的情况
for (int k = 1; k <= S[i]; k++) {
if (j >= V[i][k]) {
f[i][j] = Math.max(f[i][j], f[i - 1][j - V[i][k]] + W[i][k]);
}
}
}
}
pw.println(f[n][m]);
br.close();
pw.close();
}
}
- 题解2
import java.io.*;
import java.util.*;
public class Main {
public static final int N = 110;
public static int[][] V = new int[N][N];
public static int[][] W = new int[N][N];
public static int[] S = new int[N];
//只从前i个物品中选,选出来的物品的总体积小于等于j,满足这两个条件的价值的最大值
public static int[] f = new int[N];
public static int n, m;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
String[] str1 = br.readLine().split(" ");
n = Integer.parseInt(str1[0]);
m = Integer.parseInt(str1[1]);
for (int i = 1; i <= n; i++) {
S[i] = Integer.parseInt(br.readLine());
for (int j = 1; j <= S[i]; j++) {
String[] str2 = br.readLine().split(" ");
V[i][j] = Integer.parseInt(str2[0]);
W[i][j] = Integer.parseInt(str2[1]);
}
}
//初始化f[0][0 ~ m]都是0,所以省略
for (int i = 1; i <= n; i++) {
for (int j = m; j >= 0; j--) {
for (int k = 1; k <= S[i]; k++) {
if (j >= V[i][k]) {
f[j] = Math.max(f[j], f[j - V[i][k]] + W[i][k]);
}
}
}
}
pw.println(f[m]);
br.close();
pw.close();
}
}