[背包九讲详解] 第一讲 01背包

0-1 背包

每种物品仅有一件, 可以选择放或者不放 (01). 然后通过计算每个物品的花费, 来得到一定费用限制下能够取到的最大物品价值

基础思路

如上图所说, 设计一个状态函数来表征当前情况的状态: f[i][j] 表示在装入了 i 个物品, 占用了 j 的容量(花费了 j 数量的费用) 后物品的最大和价值. 在这个情况下, 分别用 v[i]w[i] 来储存第 i 个物品的价值和需要花费的容量.

通过这个状态函数的转移, 来获得最终的状态函数.

  • 状态转移方程的构建思路是: 从当下的 i 个物品状态下转移到 i+1 个物品的状态下时有两个选择
    • 往背包内装入新物品, 那么需要保证之前的 i 个物品都存放在 j-w[i] 的容量中: f[i-1][j-w[i]] + v[i]
    • 不放入新物品: f[i-1][j]

这样的话, 我们就能从 0 个物品时的价值出发, 一步步获得有 N 个物品的情况下, 最大的价值: max(f[N][j].

前面的步骤完成了在物品数量上的状态转移, 因此我们能够直接通过求取最后目标数量 N 的物品个数时状态函数的最大值来获取最终我们想要得到的目标.

但这个数值并不总是等同于 f[N][V] , 即不一定只有在背包完全装满的时候取到最大值. 如果希望最后直接通过调用 f[N][V] 来得到最终的最大价值, 我们在完成物品数量 i 的状态转移的同时, 还需要完成背包容量 j 的状态转移.

要进背包容量的状态转移, 只需要在状态转移方程中加上一项 f[i][j-1] 即可. 这样就可以在背包容量未满时取到最大价值的情况, 一样能转移到最终的 f[N][V] 的状态函数上.

最终的状态转移方程即为:

f[i][j] = max(f[i][j-1], f[i-1][j], f[i-1][j-w[i]]+v[i])

空间复杂度的优化

前边使用了一个二维数组来储存 i 个物品, 使用了 j 容量的情况下的状态函数. 但其实我们需要求的使用的物品数量是固定的, 也就是说我们只需要最后一个状态 f[N][V]. 那么有没有办法能够在我们只用一个一维数组, 仅仅储存容量为 j 时物品的总价值, 而将物品数量的状态转移暗含在循环的行进过程中, 从而能够减少空间复杂度呢?

我们先看之前的二维数组情况下的遍历逻辑:

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

这个时候是只考虑了物品数量的状态转移. 我们将这个循环优化为一维数组的循环, 如下图所示.

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

这里的优化过程中最精彩的部分就是第二个循环的修改. 为什么要这么做呢? 我们可以思考一下.

原始的状态转移方程是由两个在 i-1 的状态下的状态函数转移到 i 的状态下的状态函数. 那么我们在一维数组做循环的时候, 就必须要强调状态转移方程的右侧全部都还处于 i-1 的循环中. 这就意味着我们需要保证在进行每一轮的状态函数的刷新的时候, f[j-w[i]] 是在 f[j] 之后刷新的, 所以我们需要从大到小遍历进行刷新!

另外, 可能有人会注意到, 之前的时候为了保证容量也跟着一直在刷新, 加入了一个 f[i][j-1], 而在简化空间复杂度后, 这一项去掉之后仍然没有发现问题, 那么是为什么呢? 笔者暂时也还没有想的完全通透, 但是能肯定的是, 直接简单的添加一项 f[j-1] 是一定不可行的. 因为我们是从大向小遍历, 这种情况下的 f[j-1] 是还未刷新的, 等价于二维数组中的 f[i-1][j-1], 所以这样是一定不可行的. 而不添加的可行性等我想明白了再进行补充.

Python 题解

python的循环逻辑和输入逻辑个人感觉都比较傻逼一点, 所以写起来是真费劲呐. 笔者水平有限, 代码仅供参考.

v, w = [], []


n, m = map(int, input().split())

for _ in range(n):
    t1, t2 = map(int, input().split())
    v.append(t1)
    w.append(t2)
    
f = [0]*(m+1)


for i in range(n):
    j = m
    while j >= v[i] and j <= m:
        f[j] = max(f[j], f[j-v[i]] + w[i])
        j -= 1
        
print(f[m])
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值