深刻分析多重背包问题(下篇)
前言
在后面的三篇文章当中,咱们曾经认真的探讨了01背包问题和齐全背包问题以及多重背包上篇,在本篇文章当中次要给大家介绍多重背包问题的一种优化办法——二进制优化多重背包,如果你还没有看过多重背包上篇,你须要先浏览多重背包上篇。
多重背包问题介绍
有 $N$ 种物品和一个容量是 $V$ 的背包。第 $i$ 种物品最多有 $s_i$ 件,每件体积是 $v_i$,价值是 $w_i$。求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
留神:下面应用到的字符含意在本篇文章当中都一样。
多重背包问题跟01背包和齐全背包的区别都是在物品的可用次数上,01背包只能应用一次,多重背包可用应用无数次,而多重背包可用应用屡次。
多重背包的二进制优化
二进制优化的实现
在正式剖析多重背包的二进制优化之前,咱们先剖析一下多重背包的工夫复杂度,假如咱们有$N$件物品,均匀每个物品的个数为$S$,那么多重背包的的工夫复杂度为$O(NSV)$。而多重背包的二进制优化能够将这个工夫复杂度升高到$O(NV)$。
在多重背包上篇上篇当中咱们提到了多重背包的动静转移方程( $T = min(S, \frac{V}{v_i})$,其中$S$示意物品可能抉择的次数,$v_i$示意物品的体积,$V$示意以后背包的容量):
$$dp[i][j] = max\\\{ \\dp[i - 1][j], \\dp[i - 1][j - v[i]] + w[i],\\dp[i - 1][j - v[i] * 2] + w[i] * 2, \\..., \\dp[i - 1][j - v[i] * T] + w[i] * T\\\}$$
从下面的公式咱们能够晓得,对于某个有$S$件的物品当中,咱们要抉择一个适合的数字使得咱们的收益最大,这个数字可能是$1, 2, 3, 4, ..., S$。咱们在文章多重背包上篇提到咱们能够将多重背包转化成01背包,咱们将$S$个物品在逻辑上分成体积和价值雷同的$S$个不同的物品,被分成$S$个不同的物品在进行动静抉择的时候与$S$个雷同的物品是一样的。比如说对于$S$个雷同的物品$A$,咱们在抉择3个的时候收益能够达到最大,那么对于转化之后的01背包问题来说就抉择3个与$A$体积和价值雷同的物品即可。
依据下面剖析咱们能够晓得多重背包可能转化成01背包的起因就是多重背包在转化为01背包之后,01背包可能有多重背包选1个,选2个,选3个,...,选$S$个的成果。
而咱们的二进制优化也次要集中在这个中央。多重背包的二进制优化也是将多重背包问题转化成01背包问题,然而不是将$S$个雷同的物品转化成$S$个体积和价值雷同的不同的物品。依据上文的剖析咱们晓得,咱们在将多重背包转化成01背包之后是须要保障01背包可能实现多重背包选1个,选2个,选3个,...,选$S$个的成果。那么咱们如何实现这一点呢?上面代码次要显示二进制优化将多重背包转化成01背包该往装物品的价值和体积的汇合里退出什么货色。
Scanner scanner = new Scanner(System.in);int N = scanner.nextInt();int V = scanner.nextInt();ArrayList<Integer> v = new ArrayList<>();ArrayList<Integer> w = new ArrayList<>();for (int i = 0; i < N; i++) { // 这个示意第 i 个物品的体积 int vi = scanner.nextInt(); // 这个示意第 i 个物品的价值 int wi = scanner.nextInt(); // 这个示意第 i 个物品有多少个 int si = scanner.nextInt(); // 这段代码次要是实现多重背包可能抉择1个 // 抉择2个,...,抉择S个的成果 for (int j = 1; j <= si; j *= 2) { si -= j ; v.add(vi * j); w.add(wi * j); } if (si > 0) { v.add(vi * si); w.add(wi * si); }}
咱们举一个例子来剖析下面的代码,假如咱们退出一个物品$A$,它的个数为9,价值和体积别离为5和3。那么在汇合$v$和汇合$w$当中的数据别离为:
$$v = [3, 6, 12, 6]$$
$$w = [5, 10, 20, 10]$$
下面的例子将9个$A$分成了$A_1$,$A_2$,$A_3$,以及$A_4$,$A_1$到$A_4$的体积和价值别离相当于1个,2个,4个,2个的$A$的体积和价值。咱们在上文当中提到了,咱们在将多重背包转化成01背包之后是须要保障01背包可能实现多重背包选1个,选2个,选3个,...,选$S$个的成果,那么下面的转化是如何实现这个成果的呢?
- 一个$A$:相当于$A_1$。
- 两个$A$:相当于$A_2$。
- 三个$A$:相当于$A_1 + A_2$,也就是在动静抉择的时候抉择了$A_1$和$A_2$两个物品。
- 四个$A$:相当于$A_3$。
- 五个$A$:相当于$A_1 + A_3$,也就是在动静抉择的时候抉择了$A_1$和$A_3$两个物品。
- 六个$A$:相当于$A_2 + A_3$,也就是在动静抉择的时候抉择了$A_2$和$A_3$两个物品。
- 七个$A$:相当于$A_1 + A_2 + A_3$,也就是在动静抉择的时候抉择了$A_1$、$A_2$和$A_3$三个物品。
- 八个$A$:相当于$A_2 + A_3 + A_4$,也就是在动静抉择的时候抉择了$A_2$、$A_3$和$A_4$三个物品。
- 九个$A$:相当于$A_1 + A_2 + A_3 + A_4$,也就是在动静抉择的时候抉择了$A_1$、$A_2$、$A_3$和$A_4$四个物品。
置信通过下面的例子之后你曾经大抵明确了二进制优化的大抵实现过程,二进制优化也是将多重背包转化成01背包然而和之前的转化不同的是,咱们不是将$S$个物品$A$划分成$S$个体积和价值雷同的物品,而是将其划分成体积和价值是原来的物品1倍、2倍、3倍,....,$2^n$倍的物品,即$1 + 2 + 4 + ... + 2^n + a = S$,其中$a$是最初的剩下的余数($a \lt 2^{n + 1}$),比方下面最初一个2就是a = 9 - 1 - 2 - 4
。这样的划分咱们能够晓得,咱们划分之后的物品的数目会少十分多。如果物品的次数的最大值是int
类型的最大值,如果咱们一个一个的划分最多能够划分超过20亿个物品,而下面的划分形式,咱们划分进去的物品不会超过32个,因而大大降低了工夫复杂度。
之前一个一个的划分咱们的工夫复杂度为$O(NSV)$,而像下面那样划分咱们最大的工夫复杂度为$O(32NV) = O(NV)$,其中$N$示意物品的个数,$S$示意物品可能抉择的次数,$V$示意背包的容量。
下面就是咱们应用二进制优化形式将多重背包转化成01背包的形式,残缺代码如下(下方代码应用了单行数组优化):
import java.util.ArrayList;import java.util.Scanner;public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int N = scanner.nextInt(); int V = scanner.nextInt(); ArrayList<Integer> v = new ArrayList<>(); ArrayList<Integer> w = new ArrayList<>(); for (int i = 0; i < N; i++) { int vi = scanner.nextInt(); int wi = scanner.nextInt(); int si = scanner.nextInt(); for (int j = 1; j <= si; j *= 2) { si -= j ; v.add(vi * j); w.add(wi * j); } if (si > 0) { v.add(vi * si); w.add(wi * si); } System.out.println(v); System.out.println(w); } int[] f = new int[V + 1]; for (int i = 0; i < v.size(); i++) { for (int j = V; j >= v.get(i); j--) { f[j] = Math.max(f[j], f[j - v.get(i)] + w.get(i)); } } System.out.println(f[V]); }}
二进制优化的实质
咱们晓得任何一个数都有他的二进制模式,任何一个数都能够由2的整数次幂相加失去:
如果咱们有15个物品$A$,那么咱们会失去$A_1$,$A_2$,$A_3$,$A_4$,他们的价值和体积别离是$A$的1倍,2倍,4倍和8倍,这四个物品能够组成相当于任意整数倍的物品$A$的价值和分量,在这个问题当中就是1, 2, 4, 8能够组成1~15之间任意一个数。
因为咱们最终可能$S$个物品当中全副选了,因而当咱们将多重背包转化成01背包之后,所有转化之后的物品的价值和体积须要和$S$个物品雷同,而$S$不肯定恰好就是$n$个整数幂的值相加,因而在上文当中还提到了$a$,$a$就保障了咱们最终能够取到1~$S$之间任意一个数。
总结
本篇文章次要给大家介绍的多重背包问题的二进制优化,外面的逻辑还是略微有点简单的,可能须要大家认真去领会,大家在看文字的时候能够参考代码仔细分析,能够了解的更好一点。
以上就是本篇文章的所有内容了,心愿大家有所播种,我是LeHung,咱们下期再见!!!(记得点赞珍藏哦!)
更多精彩内容合集可拜访我的项目:https://github.com/Chang-LeHu...
关注公众号:一无是处的钻研僧,理解更多计算机(Java、Python、计算机系统根底、算法与数据结构)常识。