#背包问题总结
01背包
01背包就是m大小的背包中,有n个价值为w,大小为v的物品,选取物品装入背包得到最大值。
如果f数组初始化为0,那么f[n][m]就为最大值,如果只有f[0][0]初始化为0,那么只有正好装入的重量才是最大值。
朴素解法
import java.util.*;
class Main {
static int n, m;
static final int N = 1010;
static int[][] f = new int[N][N];
static int[] v;
static int[] w;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt(); // 物品个数
m = sc.nextInt(); // 总重量
v = new int[n + 1];
w = new int[n + 1];
for (int i = 1; i <= n; i ++) {
v[i] = sc.nextInt();
w[i] = sc.nextInt();
}
for (int i = 1; i <= n; i ++) {
// 遍历物品
for (int j = 0; j <= m; j ++) {
// 遍历重量
f[i][j] = f[i - 1][j]; // 第一种情况,不把物品加入
// 第二种情况,把物品加入
if (j >= v[i]) f[i][j] = Math.max(f[i][j], f[i - 1][j - v[i]] + w[i]);
}
}
System.out.println(f[n][m]);
}
}
优化解法
优化解法使用了一维数组去代替2维数组
原因是,在遍历的时候发现,i这个因素没有使用到,我们每次遍历只使用了前一次遍历的结果,而数组中正储存了前一次遍历的结果
不过就是要做一些调整,不能在使用到物品之前就把物品对应那层初始化为本层的数据了。
所以要从后往前遍历
import java.util.*;
class Main {
static int n, m;
static final int N = 1010;
static int[] f = new int[N];
static int[] v;
static int[] w;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt(); // 物品个数
m = sc.nextInt(); // 总重量
v = new int[n + 1];
w = new int[n + 1];
for (int i = 1; i <= n; i ++) {
v[i] = sc.nextInt();
w[i] = sc.nextInt();
}
for (int i = 1; i <= n; i ++) {
// 遍历物品
for (int j = m; j >= v[i]; j --) {
// 遍历重量
//f[j] = f[j]; // 第一种情况,不把物品加入
// 第二种情况,把物品加入
f[j] = Math.max(f[j], f[j - v[i]] + w[i]);
}
}
System.out.println(f[m]);
}
}
完全背包问题
朴素版本
完全背包问题改变在于,每个物品可以使用无限次
import java.util.*;
class Main {
static int n, m;
static final int N = 1010;
static int[][] f = new int[N][N];
static int[] v;
static int[] w;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt(); // 物品个数
m = sc.nextInt(); // 总重量
v = new int[n + 1];
w = new int[n + 1];
for (int i = 1; i <= n; i ++) {
v[i] = sc.nextInt();
w[i] = sc.nextInt();
}
for (int i = 1; i <= n; i ++) {
for (int j = 0; j <= m; j ++) {
// 可以使用无数次物品
// f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i], f[i - 1][j - 2*v[i]] + 2*w[i] ...)
// f[i][j - v[i]] = max(f[i - 1][j - v[i], f[i - 1][j - 2 * v[i]] + 2*w[i] ...)
// f[i][j - v[i]] 就是后半部分
// f[i][j] = max(f[i - 1][j], f[i][j - v[i]] + w[i]);
f[i][j] = f[i - 1][j];
if (j >= v[i]) {
f[i][j] = Math.max(f[i][j], f[i][j - v[i]] + w[i]);
}
}
}
System.out.println(f[n][m]);
}
}
优化版本
import java.util.*;
class Main {
static int n, m;
static final int N = 1010;
static int[] f = new int[N];
static int[] v;
static int[] w;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt(); // 物品个数
m = sc.nextInt(); // 总重量
v = new int[n + 1];
w = new int[n + 1];
for (int i = 1; i <= n; i ++) {
v[i] = sc.nextInt();
w[i] = sc.nextInt();
}
for (int i = 1; i <= n; i ++) {
for (int j = 0; j <= m; j ++) {
// 我们之前逆序遍历,是因为要使用上一层的数据,不想提前刷新
// 这次就不一样了,我们想要使用的就是本层数据,所以不用逆序
if (j >= v[i]) {
f[j] = Math.max(f[j], f[j - v[i]] + w[i]);
}
}
}
System.out.println(f[m]);
}
}
多重背包
多重背包就是物品有限制用多少次
import java.util.*;
class Main {
static int n, m;
static final int N = 1010;
static int[][] f = new int[N][N];
static int[] v;
static int[] w;
static int[] t;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt(); // 物品个数
m = sc.nextInt(); // 总重量
v = new int[n + 1];
w = new int[n + 1];
t = new int[n + 1];
for (int i = 1; i <= n; i ++) {
v[i] = sc.nextInt();
w[i] = sc.nextInt();
t[i] = sc.nextInt();
}
for (int i = 1; i <= n; i ++) {
// 遍历物品
for (int j = m; j >= 0; j --) {
f[i][j] = f[i - 1][j]; // 不拿
// 拿 所以遍历有多少个可以
for (int k = 1; k <= t[i] && k * v[i] <= j; k ++) {
f[i][j] = Math.max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
}
}
}
System.out.println(f[n][m]);
}
}
多重背包的二进制优化
这个思想很巧妙
就是把一个背包使用的次数我们可以进行拆分。把多重背包看作01背包问题,那么思路就简单了
比如是一个重量为v,价值为w的物品,可以使用10次
可以把10分成 1, 2, 4, 3
那么这个物品就可以被分成:1 * v, 1 * w ; 2 * v, 2 * w; 4 *v , 4 * w, 3 * v, 3 *w
这四个物品,去分别当成一个物品遍历;
import java.util.*;
class Main {
static int n, m;
static final int N = 2010;
static int[] f = new int[N];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt(); // 物品个数
m = sc.nextInt(); // 总重量
List<Goods> list = new ArrayList<>();
for (int i = 1; i <= n; i ++) {
int v = sc.nextInt();
int w = sc.nextInt();
int t = sc.nextInt();
for (int k = 1; k <= t; k *= 2) {
t -= k;
list.add(new Goods(v * k, w * k));
}
if (t > 0) list.add(new Goods(t * v, t * w));
}
for (Goods good : list) {
// 遍历物品
for (int j = m; j >= good.v; j --) {
f[j] = Math.max(f[j], f[j - good.v] + good.w);
}
}
System.out.println(f[m]);
}
static class Goods {
int v;
int w;
public Goods(int v, int w) {
this.v = v;
this.w = w;
}
}
}
多重背包
import java.util.*;
class Main {
static int n, m;
static int[] f, v, w;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
f = new int[m + 1];
for (int i = 0; i < n; i ++) {
int s = sc.nextInt();
v = new int[s];
w = new int[s];
for (int j = 0; j < s; j ++) {
v[j] = sc.nextInt();
w[j] = sc.nextInt();
}
for (int j = m; j >= 0; j --) {
for (int k = 0; k < s; k ++) {
if (j >= v[k]) {
f[j] = Math.max(f[j], f[j - v[k]] + w[k]);
}
}
}
}
System.out.println(f[m]);
}
}