二进制分组优化背包

算法笔记

关于名字

名字看不懂无所谓,我看有一篇博客这么叫感觉有点道理就也这么叫了,dls 管这个叫数位背包,但那样叫就感觉有点自成一派,其实就是一种特殊的 01 背包。

引入

直接看一个例题:[HNOI2007]梦幻岛宝珠,题意就是一个容量很大的01背包问题,但ww能写成 a×2b,a10,b30a\times 2^b, a\le 10, b\le 30的形式。

这个特殊限制就开始暗示我们从二进制的角度考虑,我们要做的是将物品按bb分组,从大到小,每次考虑一个分组里我物品。当对分组ii进行dp时,设当前剩余的容量为k×2ik\times 2^i,由于分组里物品重量都能写成a×2ia\times 2^i的形式,所以只有容量的系数kk会变化,所以我们可以只用系数来代表当前剩余容量。

其实最重要的限制是 a10a\le 10,因为任何数都能写成 a×2ba\times 2^b 的形式,只不过 aa 很大罢了。 aa很小这个限制就确保了整个组里所有物品的重量之和不超过 100×10×2b100\times 10 \times 2^b(100来自于题目中最多有100个物品的限制)。再结合上一段中用系数表示容量的方法,这样重量的状态就从标准01背包做法的10910^9缩小到了1000!这就是本题的核心思想。

接下来我们说状态转移,令dpi,jdp_{i, j}为考虑完ii及以上的组之后,背包剩余容量为j×2ij\times 2^i时能得到的最高价值(jj的上限为1000,因为再多了后面的物品也用不完)。在一个分组内dp时就是常规的01背包,重点在于从ii转移到i1i-1:当前剩余容量j×2ij\times 2^i 变成 2×j×2i1+si1×2i1=(2×j+si1)2i12 \times j \times 2^{i-1} + s_{i-1} \times 2^{i-1} = (2\times j + s_{i-1})2^{i-1},其中si1s_{i-1}代表背包总容量二进制表示的第i1i-1位,现在加上si1×2i1s_{i-1}\times 2^{i-1}是因为之前这部分容量太小了用不上,在考虑i1i-1组的时候就能用上了。用代码表示一下转移的话就是

dp[i - 1][j * 2 + s[i - 1]] = max(dp[i - 1][j * 2 + s[i - 1]], dp[i][j]);

另一道题

接下来看一道不是很“背包”的题CF1670F. Jee, You See?,核心思路不变。变了的地方及对应的解决方法:

  • 最大重量变成了方法数
    • max变成+
  • 物品变成了数组中第ii位1的个数,可以选从0到n个
    • 相当于上面的题中的aa从0到nn各一个,遍历即可
  • 异或的限制
    • 如果当前位是11,那就选1,3,5,1,3,5,\dots个,如果是00就选0,2,4,0,2,4,\dots