1. 问题描述:
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。第 i 件物品的体积是 vi,价值是 wi。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0 0
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8
来源:https://www.acwing.com/problem/content/2/
2. 思路分析:
① 01背包是经典的动态规划问题,其实有两种比较常规的写法,第一种是使用二维列表(c/c++/java使用二维数组)表示,其中dp[i][j]表示前i个物品背包容量为j的情况下能够获得的最大价值,递推的关系式也比较好想,对于当前的第i件物品可以拿也可以不拿所以存在两种状态,也即在背包容量允许放当前的物品的时候尝试拿取当前的物品放进背包或者不拿取当前的物品从而计算出当前的dp值,递推方程为:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + v[i])。我们可以进一步对二维列表进行空间上的优化,可以将其优化为一维列表,其中dp[i]表示当前背包容量为i的时候最大价值,当为一维列表的时候那么我们在计算当前的dp[i]的时候需要逆序遍历当前的背包容量,尝试将当前的物品到达当前容量的背包中,为什么要逆序呢?其实举出一个简单的例子那么就非常好理解了,背包问题本质上是在填写二维表格中的数字,下面是物品的重量weight为[4, 7, 5],价值value为[5, 9, 6]的例子。
② 根据①中的例子列出在正序与逆序遍历背包容量的二维表格,如下表所示,可以发现其实在正序遍历背包容量递推第一行的dp值开始就错了,那么后面第二行...的也会跟着错,在正序遍历的时候dp[i]表达的意思已经发生了变化,例如填写第一行的时候dp[4] = 4....dp[8] = max(dp[8], dp[4] + value[4]) = 10这个时候递推的dp[8]的结果就是错的,此时dp[8]其实是将第一个物品放入到容量为8的背包中两次。而逆序的时候就不会存在上面的情况,每一行表格中的数字反应了递推过程的变化情况。
3. 代码如下:
二维列表:
if __name__ == '__main__':
# n个物品 背包容量为v
n, v = map(int, input().split())
dp = [[0] * (v + 1) for i in range(n + 1)]
# 第i个物品的价值与重量
vi, wi = [0], [0]
for i in range(n):
a, b = map(int, input().split())
wi.append(a)
vi.append(b)
for i in range(1, n + 1):
for j in range(1, v + 1):
if j >= wi[i]:
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - wi[i]] + vi[i])
else:
dp[i][j] = dp[i - 1][j]
print(dp[n][v])
一维列表:
if __name__ == '__main__':
n, v = map(int, input().split())
# 优化为一维列表
dp = [0] * (v + 1)
# 在列表的第一个位置插入0元素这样在后面的时候可以直接使用下标处理
vi, wi = [0], [0]
for i in range(n):
a, b = map(int, input().split())
wi.append(a)
vi.append(b)
for i in range(1, n + 1):
# 逆序遍历背包容量
for j in range(v, wi[i] - 1, -1):
dp[j] = max(dp[j], dp[j - wi[i]] + vi[i])
print(dp[v])