第 1 版:设置二维状态数组,根据最朴素的「状态转移思想」
Java 代码:
import java.util.Scanner;
public class Main {
// 完全背包问题
// 判题地址:https://www.acwing.com/problem/content/3/
// 参考资料:https://www.acwing.com/solution/acwing/content/5345/
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读第 1 行
int N = scanner.nextInt();
int V = scanner.nextInt();
// 读后面的体积和价值
int[] weight = new int[N];
int[] value = new int[N];
for (int i = 0; i < N; i++) {
weight[i] = scanner.nextInt();
value[i] = scanner.nextInt();
}
int[][] dp = new int[N][V + 1];
// 先写第 1 行
for (int k = 0; k * weight[0] <= V; k++) {
dp[0][k * weight[0]] = k * value[0];
}
// 最朴素的做法
for (int i = 1; i < N; i++) {
for (int j = 0; j <= V; j++) {
// 多一个 for 循环,枚举下标为 i 的物品可以选的个数
for (int k = 0; k * weight[i] <= j; k++) {
dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - k * weight[i]] + k * value[i]);
}
}
}
// 输出
System.out.println(dp[N - 1][V]);
}
}
做等量代换没有用。
dp[i][j] = max (dp[i - 1][j],
dp[i - 1][j - v[i]] + w[i],
dp[i - 1][j - 2 * v[i]] + 2 * w[i],
...,
dp[i - 1][j - k * v[i]] + k * w[i])
但是直接从语义出发就比较好理解了,因为每次多考虑的那个物品,也可以从选和不选来考虑,只不过,每次多选的额这个物品参考的当前行的数值。
dp[i][j] = max(dp[i - 1][j], dp[i][j - v[i]) + w[i])
说明:
1、dp[i - 1][j]
表示的是「上一行」的数值,对应的情况是:当前下标为 i
的东西「不值钱」,又占地方又廉价,一个都不拿;
2、dp[i][j - v[i]
表示的是「这一行」的数值,对应的情况是:当前下标为 i
的东西「物小且价格昂贵」,又占不地方又值钱,在不超过背包容量的情况下,应该多拿。
第 2 版:设置二维状态数组,填表发现状态转移的规律:之和当前行有关,并且设置了「哨兵」行
Java 代码:
import java.util.Scanner;
public class Main {
// 完全背包问题
// 判题地址:https://www.acwing.com/problem/content/3/
// 参考资料:https://www.acwing.com/solution/acwing/content/5345/
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读第 1 行
int N = scanner.nextInt();
int V = scanner.nextInt();
// 读后面的体积和价值
int[] weight = new int[N];
int[] value = new int[N];
for (int i = 0; i < N; i++) {
weight[i] = scanner.nextInt();
value[i] = scanner.nextInt();
}
int[][] dp = new int[N + 1][V + 1];
// 先写第 1 行
// 优化
for (int i = 1; i <= N; i++) {
for (int j = 0; j <= V; j++) {
// 至少是上一行抄下来
dp[i][j] = dp[i - 1][j];
if (weight[i - 1] <= j){
dp[i][j] = Math.max(dp[i][j], dp[i][j - weight[i - 1]] + value[i - 1]);
}
}
}
// 输出
System.out.println(dp[N][V]);
}
}
第 3 版:设置二维状态数组,填表发现状态转移的规律:之和当前行有关
Java 代码:
import java.util.Scanner;
public class Main6 {
// 完全背包问题
// 判题地址:https://www.acwing.com/problem/content/3/
// 参考资料:https://www.acwing.com/solution/acwing/content/5345/
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读第 1 行
int N = scanner.nextInt();
int V = scanner.nextInt();
// 读后面的体积和价值
int[] weight = new int[N];
int[] value = new int[N];
for (int i = 0; i < N; i++) {
weight[i] = scanner.nextInt();
value[i] = scanner.nextInt();
}
int[] dp = new int[V + 1];
// 先写第 1 行
// 状态压缩
for (int i = 1; i <= N; i++) {
// 细节,j 从 weight[i - 1] 开始遍历
for (int j = weight[i - 1]; j <= V; j++) {
dp[j] = Math.max(dp[j], dp[j - weight[i - 1]] + value[i - 1]);
}
}
// 输出
System.out.println(dp[V]);
}
}
参考资料:
https://www.acwing.com/solution/acwing/content/3986/
https://www.acwing.com/solution/acwing/content/5345/
https://www.acwing.com/solution/acwing/content/5428/
https://www.acwing.com/solution/acwing/content/5929/
https://www.acwing.com/solution/acwing/content/7493/
视频讲解:
https://www.bilibili.com/video/av91941737?p=2
https://www.bilibili.com/video/av70148899?p=2