完全背包是01背包的衍生题,01背包的每一件物品数量都是1,但是当我们将题目修改成每一个物品无限拿取的时候,再去使用01背包那样顺序拿取的方法显然不适用了。
那么这个问题应该如何从哪开始思考呢?
首先,既然我们不能考虑只拿一个了,而且同时我们也不能拿无限个,这时候就有一个限制我们拿去某项物品的数目乘以体积不能大于背包的当前空间,根据这个条件我们可以得到这样一个递推方程:
很明显,这个递推方程式在原来01背包的基础上,新增了一个k的维度,这也就意味着我们将多一重循环,朴素的二维背包写法是没有什么难度的,基本上和01背包没有差别。但是对于完全背包的一维空间优化来说,有一些和01背包不一样的地方。
通过下main这张图我们可以得到

两个相近的递推方程式之间合并后可以得到一个更短的递推式,也就是在每次计算dpij的时候我们只需要引用之前dpi(j-vi)的状态和dpi-1j的状态就可以地推出当前状态了。通过这个递推式,我们可以规划出一种更好的循环方式,将k这一层循环同过改变循环顺序的方式消除掉。下面是完全背包的二维实现。
for(int i = 0 ; i < n ; i ++){
for(int j = 0 ; j < w ; j ++){
if(j>=w[i])dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
}
}同理,完全背包的递推式也完全符合之前01背包的特性,很显然也可以用一维空间优化来优化数组大小
for(int i = 0 ; i < n ; i ++){
for(int j = 0 ; j < w ; j ++){
if(j>=w[i])dp[j] = max(dp[j],dp[j-w[i]]+v[i]);
}
}到这一步,我们可以看出,完全背包一维优化的遍历方式似乎和01背包的二维实现十分相似,下面是01背包的二维实现:
for(int i = 0 ; i < n; i ++){
for(int j = 0 ; j < w ; j ++){
dp[i][j] = dp[i-1][j];
dp[i][j] = max(dp[i][j],dp[i-1][j-w[i]]+v[i]);
}
}那么这两者有什么区别,可以让他们一个解决01一个解决完全这两种不同的背包问题呢?
关键点在于,上面的完全背包的一维实现,dpij这个状态来源于当前行的正上方,和当前行的左边。而01背包的二维实现,则是根据当前行的正上方,和左上方的状态来决定的。这也就是为什么01背包的一维实现我们需要到着来的原因,因为如果是顺着来的,就会转化成完全背包的一维实现了。