[背包九讲详解] 第二讲 完全背包

完全背包

完全背包问题与01背包问题背景类似, 同样是有 N 个物品分别具有自己的体积 v[i] 和价值 w[i]. 但不同的是, 完全背包中的每一个物品都可以无限次被取用, 没有次数限制.

基础思路

首先从状态转移的基础思路出发, 这道题和 01 背包有很多的相似之处. 我们可以借鉴 01 背包状态函数的设计思路, 同时在这个过程中注意两者的关键不同: 物品的多次使用. 物品能够多次被使用, 意味着我们在计算一个函数的状态的时候, 不仅仅需要思考这个物品是否被使用, 还需要思考这个物品被使用了几次.

直观的思路是在原始的二重循环物品 i 和占用了的体积 j 之外再使用一个变量 k 来遍历每个物品使用的次数.

for i in range(N):
	for j in range(V):
		while k*v[i] + j <= V:
			f[i][j] = max(f[i-1][j], f[i][j-k*v[i]] + k*w[i])
			k += 1

但显而易见的, 这样的算法复杂度为 O(NV*V/v[i]), 需要进一步优化. 优化的方法不止一种, 比如 背包九讲 中提到的使用二进制思想优化, 再比如即将介绍的一维数组的优化. 这里笔者直接介绍平时使用的, 时间复杂度最低的方法, 做详细解释.

从 01 背包出发的一维简化

首先来看看 01 背包的一维数组状态转移的写法:

for i in range(N):
	for j in range(V+1)[::-1]: # 注意这个地方的倒序循环
		f[j] = max(f[j], f[j-w[i]] + v[i])

这里复习一下, 当时使用倒序遍历 j 的原因是什么呢? 是我们希望在计算每一次 f[j] 的时候, 都能够保证 f[j-w[i]] 没有刷新. 为什么我们希望没有刷新呢? 因为我们希望保证这个时候的状态函数一定是未曾使用过第 i 件物品的状态函数, 从而满足 01 背包的限制: 每个物品只能使用一次.

这意味着什么? 意味着如果我们希望能够重复计算每个物件, 让它们能够被多次使用, 那么我们只需要把遍历顺序正过来!

for i in range(N):
	j = v[i]
    while j >= v[i] and j <= m:
        f[j] = max(f[j], f[j-v[i]] + w[i])
        j += 1

这样就可以解决完全背包问题, 同时能够保证更小的时间和空间复杂度.

这样的论证缺乏足够的说服力, 接下来使用数学归纳法将这个状态转移的过程能够解决完全背包问题证明一下.

  1. 初始状态下, 第一件物品时, 能够保证 f[j](1) 是此时的最优解, 这个是显而易见的.
  2. 假设在计算第 i-1 件物品时, f[j] 是最优解. 这里为了区分物品遍历时的次数, 将状态函数写为 f[j](i-1)
  3. 第 i 件物品时: f[j](i) = max(f[j](i-1), f[j-v[i]](i) + w[i])
    1. 从最小开始遍历: f[v[i]](i) = max(f[v[i]](i-), f[0] + w[i]), 这个时候显而易见我们一定会得到对于体积为 v[i] 的情况下的最优解.
    2. 此时, f[j](i-1) 根据前面的假设, 已经是不添加第 i 件物品的情况下, 体积为 j 时的最优解, 而 f[j-v[i]](i) + w[i] 也是添加 i 物品的情况下, 体积为 j 的最优解, 因为此时我们是从小向大遍历, 我们已经保证了 f[j-v[i]](i) 是体积为 j-v[i] 时, 加入第 i 件物品情况下的最优解.

所以完全背包的这种算法被证明.

Python 题解

笔者水平有限, 代码仅供参考.

N, V = map(int, input().split())
v, w = [], []

for _ in range(N):
    t1, t2 = map(int, input().split())
    v.append(t1)
    w.append(t2)
    
f = [0]*(V+1)
    
for i in range(N):
    j = v[i]
    while j<= V:
        f[j] = max(f[j], f[j-v[i]] + w[i])
        j += 1
        
print(f[V])
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值