Loading docs/dp/knapsack.md +45 −0 Original line number Diff line number Diff line Loading @@ -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; } ``` Loading
docs/dp/knapsack.md +45 −0 Original line number Diff line number Diff line Loading @@ -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; } ```