特征:每个物品的个数有限
集合划分跟完全背包一样,只不过k有上限s[i]

一、暴力解法

  1. import java.util.Scanner;
  2. public class Main{
  3. public static void main(String[] args) {
  4. Scanner in = new Scanner(System.in);
  5. int N = in.nextInt();
  6. int V = in.nextInt();
  7. int[] v = new int[105];
  8. int[] w = new int[105];
  9. int[] s = new int[105];
  10. int[][] f = new int[105][105];
  11. for (int i = 1; i <= N; i++) {
  12. v[i] = in.nextInt();
  13. w[i] = in.nextInt();
  14. s[i] = in.nextInt();
  15. }
  16. // 时间复杂度为NVS
  17. for (int i = 1; i <= N; i++) {
  18. for (int j = 0; j <= V; j++) {
  19. for (int k = 0; k <= s[i] && k * v[i] <= j; k++) {
  20. f[i][j]=Math.max(f[i][j],f[i - 1][j-k*v[i]]+k*w[i]);
  21. }
  22. }
  23. }
  24. System.out.println(f[N][V]);
  25. }
  26. }

二、与完全背包优化的区别

完全背包是求前缀的最大值,而多重背包是求固定长度的窗口内的最大值

  1. // 完全背包中 k 无上限
  2. f[i][j] = max(f[i-1][j], f[i-1][j-v]+w, f[i-1][j-2*v]+2*w,...f[i-1][j-k*v]+k*w)
  3. f[i][j-v] = max(f[i-1][j-v], f[i-1][j-2*v]+w, f[i-1][j-3*v]+2*w,...f[i-1][j-k*v]+k*w)

k无上限所以直接用f[i][j-v]替代 f[i][j] 后面的部分
但多重背包问题中每个物品的上限为s[i]

  1. // 多重背包问题中每个物品的上限为s[i],用s表示
  2. f[i][j] = max(f[i-1][j], f[i-1][j-v]+w, f[i-1][j-2*v]+2*w,...f[i-1][j-s*v]+s*w)
  3. f[i][j-v] = max(f[i-1][j-v], f[i-1][j-2*v]+w,...f[i-1][j-s*v]+(s-1)*w + f[i-1][j-(s+1)*v]+s*w)

可以看出f[i][j-v]f[i][j]多了一项f[i-1][j-(s+1)*v]+s*w,这一项无法去除,故无法用完全背包的优化思路

三、二进制优化思路

1 、需要明白一个前提

我们知道任意一个实数可以由二进制数来表示,也就是2^0~2^k其中一项或几项的和。
这个前提其实就是二进制转化成十进制的方法,1110 (2) => 23+22+21+0*20=14
反过来就是 14 可以用 23、22、21这三个数字组成

2、针对题目

所以每个物品的s[i]可以用若干堆2的幂次方个数来表示,以此可优化为01背包问题
但与二进制表示数不同的是此题中的优化必须是按顺序来的,即:20+21+22+23...2k
二进制表示中是不按顺序的

比如:
10 在二进制可以表示为 23+21
此题中如果10个物品则只能表达为 20 + 21 + 22 + 3
堆数为log210的上取整=4堆

即如果s不是正好可以分为每个组都是2的幂个的
那么就可以划分成20+21+22+23...2k +c = 2k+1-1 +c = s``c < 2k+1
下图为转化过程
3780699CADABDBD62A229253AF3C005D.png
原为 3个物品,每个物品个数为s 的多重背包问题,二进制优化后转换成了 有log2SA+log2SB+log2SC(均为上取整)个物品,每个物品个数为1的01背包问题。
因为优化物品数量 发生变化,所以 v[]、w[] 数组开辟的空间也需要发生改变,之前是v[N],现在是v[log2SA+log2SB+log2SC(均为上取整)]

例如:
image.png
优化前开辟v[1005] w[1005] s[1005],优化后开辟N*log si ,log2000上取整是11,故应开辟1000*11+5(+5防越界),故开辟v[11005]

四、优化后代码

时间复杂度由NVS变为Nlog2S *V=NVlogS

  1. import java.util.Scanner;
  2. public class Main{
  3. public static void main(String[] args) {
  4. Scanner in = new Scanner(System.in);
  5. int N = in.nextInt();
  6. int V = in.nextInt();
  7. int[] v = new int[11005];
  8. int[] w = new int[11005];
  9. int[] f = new int[11005];
  10. int cnt = 0;
  11. for (int i = 1; i <= N; i++) {
  12. int a,b,s;
  13. a = in.nextInt();
  14. b = in.nextInt();
  15. s = in.nextInt();
  16. int k = 1;
  17. while (k <= s) {
  18. cnt ++;
  19. v[cnt] = a * k;
  20. w[cnt] = b * k;
  21. s -= k;
  22. k *= 2;
  23. }
  24. if (s > 0) {
  25. cnt++;
  26. v[cnt] = a * s;
  27. w[cnt] = b * s;
  28. }
  29. }
  30. N = cnt;
  31. for (int i = 1; i <= N; i++) {
  32. for (int j = V; j >= v[i]; j--) {
  33. f[j] = Math.max(f[j],f[j -v[i]] + w[i]);
  34. }
  35. }
  36. System.out.println(f[V]);
  37. }
  38. }