01背包问题属于组合优化问题:假设你要出门旅游,你现在有一个书包,这个书包的容量(capacity)有限,有很多物品如牙刷、防晒霜、雨伞、水杯等等,但书包装不下所有物品,因此我们必须有所取舍。那么通常我们怎么取舍的呢?一般我们会选择那些重要的物品,这个重要性我们可以量化为一个数字(value)。此时背包问题就抽象为:
给定n个物品,容量为capacity的背包。每个物品自身体积ws=[w1, w2,w3...],每个物品的自身价值vs=[v1, v2, v3...],求书包在容量不超限的情况下,装入物品的最大价值是多少?(w1表示第一个物品的体积,v1表示第一个物品的价值)。
动态规划求解思想:问题可以由类似的子问题进行求解,定义数组形式来表示问题的解--->初始化数组的边界值--->递推求解整个数组值。
那么根据该思想,背包问题的动态规划思路建立过程如下:
第一步:定义数组形式的问题解。定义dp[i][j],表示背包容量为j时,前i个物品能够组合出来的最大价值。先不考虑怎么推导,思考一下该定义下我们问题的最终解是什么???应当是dp[n][capacity], 即背包容量为capacity时,前n个物品的组合出来的最大价值。那么怎么想出来的要这么定义的呢?不同动态规划问题的数组定义方式不一样,如何定义数组也是最难的。根据个人经验,该数组定义时考虑控制问题规模的量,背包问题中是:物品个数可多些可少些,背包容量可大些可小些。我们说过动规是有子问题形式的,这个子问题隐藏着问题规模的变化,因此物品个数和背包容量控制着问题规模的变化,我们考虑用它们来表示背包问题的解。
第二步:初始化数组边界值。容易想到,dp[i][0],即背包容量为0时,前i个物品的最优价值一定是0,因为容量为0,说明没有物品能放入背包,那么背包价值为0。同样dp[0][j]=0,因为前0个物品的也没有价值。 此处需要注意一点:我们问题的最终解为dp[n][capacity], 在C++中,定义int dp[10],访问时只能访问到dp[9],因为数组下标从0开始。因此C++中定义形式是dp[n+1][capacity+1]
第三步:递推,简历动态规划方程。所有动规方程的建立都是考虑一点,数组形式下的dp[i][j]等于什么?即如何让已经求得的某些dp值来表示dp[i][j]。考虑dp[i][j]表示背包容量为 j 时,前i个物品的最优价值,对其求解时dp[i-1][j]是已知的,那么第 i 物品是否应该放入背包呢?第一种情况 i 物品的体积很大, 大于 j ,也即即便将背包里已有物品全挪走,i 物品也放不下背包中,此时dp[i][j] = dp[i-1][j]。 第二种情况 i 物品体积比背包容量 j 小,也即如果背包可以腾出来 i 物品体积大小的地方,但是我们要想一下:要把i 放进去,会挪出来一部分物品,此时背包价值变为dp[i-1][j-wi]+vi, 即腾出来wi体积的地方后,剩余j-wi的地方放前i-1个物品的组合情况,然后该情况加上第i个物品价值即可。而如果我们不放入i物品,此时背包价值为dp[i-1][j], 因此最大的价值是dp[i][j]=max(dp[i-1][j-wi]+vi, dp[i-1][j])。
python代码如下:
# 第一行输入物品个数n和背包容量capacity
# 第二行输入物品体积w[]
# 第三行输入物品价值v[]
n, capacity = list(map(int, input().split()))
ws = list(map(int, input().split()))
vs = list(map(int, input().split()))
ws = [0] + ws
vs = [0] + vs
dp = [[0]*(capacity+1) for i in range(n+1)] #dp[i][j]表示前i个物品,占据了j体积时的最优价值
for i in range(1, n+1):
for j in range(1, capacity+1):
if ws[i]>j: #第i个物品放不下时,最优价值与前i-1个物品最优价值一致
dp[i][j] = dp[i-1][j]
else: #放得下时,因为要求放入i后剩余容量仍未j,那么势必有其他物品被取出,此时最大价值应考虑两部分:max(不放入i的最优价值,放i后的价值)
dp[i][j] = max(dp[i-1][j], dp[i-1][j-ws[i]]+vs[i])
print(dp[n][capacity])