Unverified Commit 44a0d40a authored by ir1d's avatar ir1d Committed by GitHub
Browse files

Update knapsack.md (#1180)

Update knapsack.md
parents 9f9ccf66 f832e276
Loading
Loading
Loading
Loading
+45 −0
Original line number Diff line number Diff line
@@ -117,3 +117,48 @@ $$
    ```

## 多重背包

多重背包也是 0-1 背包的一个变式。与 0-1 背包的区别在于每种物品可以选 $k_i$ 次,而非 $1$ 次。

一个很朴素的想法就是:把“每种物品选 $k_i$ 次”等价转换为“有 $k_i$ 个相同的物品,每个物品选一次”。这样就转换成了一个 0-1 背包模型,套用上文所述的方法就可已解决。时间复杂度:$O(nW\sum k_i)$,可以轻松 TLE。

虽然我们失败了,但是这个朴素的想法可以给我们的思路提供一些借鉴——我们可以把多重背包转化成 0-1 背包模型来求解。

显然,复杂度中的 $O(nW)$ 部分无法再优化了,我们只能从 $O(\sum k_i)$ 处入手。

为了表述方便,我们用 $A_{i,j}$ 代表第 $i$ 种物品拆分出的第 $j$ 个物品。

在朴素的做法中,$\forall j\le k_i$,$A_{i,j}$ 均表示相同物品。那么我们效率低的原因主要在于我们进行了大量重复性的工作。举例来说,我们考虑了 “同时选$A_{i,1},A_{i,2}$” 与 “同时选 $A_{i,2},A_{i,3}$” 这两个完全等效的情况。这样的重复性工作我们进行了许多次。那么优化拆分方式就成为了解决问题的突破口。

我们可以通过“二进制分组”的方式使拆分方式更加优美。

具体地说就是令 $A_{i,j}\left(j\in\left[0,\lfloor \log_2(k_i+1)\rfloor-1\right]\right)$ 分别表示由 $2^{j}$ 个单个物品“捆绑”而成的大物品。特殊地,若 $k_i+1$ 不是 $2$ 的整数次幂,则需要在最后添加一个由 $k_i-2^{\lfloor \log_2(k_i+1)\rfloor-1}$ 个单个物品“捆绑”而成的大物品用于补足。

举几个例子:

- $6=1+2+3$
- $8=1+2+4+1$
- $18=1+2+4+8+3$
- $31=1+2+4+8+16$

显然,通过上述拆分方式,可以表示任意 $\le k_i$ 个物品的等效选择方式。将每种物品按照上述方式拆分后,使用 0-1 背包的方法解决即可。

时间复杂度 $O(nW\sum\log k_i)$

??? 二进制分组代码

    ```
        index = 0;
        for(int i = 1; i <= m; i++) {
            int c = 1, p, h, k;
            cin >> p >> h >> k;
            while(k - c > 0) {
                k -= c;
                list[++index].w = c * p;
                list[index].v = c * h;
                c *= 2;
            }
            list[++index].w = p * k; 
            list[index].v = h * k;
        }
    ```