前面讲了什么是完全背包问题,完全背包问题就是每个物品你可以使用无数次,而今天所要说的多重背包问题,每个物品都对应有限的数量。这个问题与完全背包问题很相似,只需要在完全背包问题的状态转移方程上稍微做以改动即可。
时间复杂度为
import java.util.Arrays;
import java.util.Scanner;
public class Bag_muti {
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner sc = new Scanner(System.in);
System.out.println("请输入物品的个数: ");
int N = Integer.parseInt(sc.nextLine());
System.out.println("请输入背包的大小: ");
int V = Integer.parseInt(sc.nextLine());
System.out.println("请输入各个物品的价值: ");
int[] W = new int[N];
String[] str1 = sc.nextLine().split(" ");
for (int i=0; i < N; i++) {
W[i] = Integer.parseInt(str1[i]);
}
int[] C = new int[N];
System.out.println("请输入各个物品的花费: ");
String[] str2 = sc.nextLine().split(" ");
for (int i=0; i < N; i++) {
C[i] = Integer.parseInt(str2[i]);
}
int[] M = new int[N];
System.out.println("请输入各个物品的个数: ");
String[] str3 = sc.nextLine().split(" ");
for (int i=0; i < N; i++) {
M[i] = Integer.parseInt(str3[i]);
}
System.out.println("您输入的物品价值数组为: ");
System.out.println(Arrays.toString(W));
System.out.println("您输入的物品的花费数组为: ");
System.out.println(Arrays.toString(C));
System.out.println("您输入的物品的个数数组为: ");
System.out.println(Arrays.toString(M));
int res = maxValue1(N, V, W, C, M);
System.out.println("最大价值为:");
System.out.println(res);
}
private static int maxValue1(int N, int V, int[] W, int[] C, int[] M) {
int [][] F = new int[N+1][V+1];
for (int i = 0; i < N+1; i++) {
F[i][0] = 0;
}
for (int j = 0; j < V+1; j++) {
F[0][j] = 0;
}
for (int i=1; i < N+1; i++) {
for (int j=1; j < V+1; j++) {
int res = F[i-1][j-1];
for (int k=0; k*C[i-1]<=j && k<=M[i-1]; k++) {
int tmp = F[i- 1][j-k*C[i-1]] + k*W[i-1];
if (tmp > res) {
res = tmp;
}
}
F[i][j] = res;
}
}
return F[N][V];
}
}
当然我们也可以像完全背包问题那样,将多重背包问题转化为0、1背包问题。而如果仅仅是将第i件物品复制M[i]份,那么时间复杂度是不会减小的,因此想到也可以使用二进制优化的方法解决多重背包问题。将i种物品分成若干件0、1背包中的物品,其中每件物品都有一个系数,这件物品的费用及价值都等于原来的费用和价值乘以这个系数。系数的求法就是之前提到的二进制优化方法,例如13
由于这四项的和刚好大于13,而我们不能让这几个数组合出大于13的整数,因此我们最后一位应该是13-1-2-4=6
因此我们可以知道0~13之间所有的数都可以使用0、1、2、4、6组合出来,因此这样转化为0、1规划问题,时间复杂度为
这个是背包九讲中给出的伪代码,第一次看时我是没太看懂的,下面我们可以写出代码来看这个程序到底是咋回事。
首先就是实现CompletePack() ZeroOnePack()两个函数
public class Bag_muti {
static Scanner sc = new Scanner(System.in);
public static int N = Integer.parseInt(sc.nextLine());
static int[] dp = new int[N];
// 省略主函数,前面有
private static void ZeroPack(int c, int w, int V) {
// c为当前物品的花费,w为当前物品的价值,V为背包总体积,本函数相当于用当前遍历到的物品来更新dp函数
for (int i=V; i>=c; i--) {
dp[i] = Math.max(dp[i-1], dp[i-1]+w);
}
}
private static void CompletePack(int c, int w, int V) {
// c w V的定义与上相同,是完全背包的更新方法,与0、1背包只有细微差别
for (int i=c; i<=V; i++) {
dp[i] = Math.max(dp[i-1], dp[i-1]+w);
}
}
现在分析上述伪代码,我们还是像前几个背包问题一样,对每个物品进行遍历,如果将所有当前遍历到的物品都放入背包,其花费的空间要大于背包的容量,那就相当于这个物品可以无限的取,因为是永远也取不完的,这时候对于当前物品可以用完全背包的方法来套用。反之,如果可以取完所有的物品,那么我们就需要选择取多少个当前物品,也就是要使用二进制优化方法。
private static int maxValue2(int N, int V, int[] W, int[] C, int[] M) {
// dp里的元素先全部赋予0
for (int i=0; i < N; i++) {
dp[i] = 0;
}
for (int i=1; i < N; i++) {
// 如果将所有当前物品都放入背包花费超过背包总容积的话 就用完全背包套
if (M[i]*C[i] > V) {
CompletePack(C[i], W[i], V);
}
else {
int k = 1;
while(k < M[i]) {
ZeroPack(k*C[i], k*W[i], V);
M[i] -= k;
k *= 2;
}
if (M[i] > 0) {
ZeroPack(M[i]*C[i], M[i]*W[i], V);
}
}
}
return dp[V];
}