题目描述
小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的空间,并且具有不同的价值。
小明的行李空间为 N,问小明应该如何抉择,才能携带最大价值的研究材料,每种研究材料只能选择一次,并且只有选与不选两种选择,不能进行切割。
输入描述
第一行包含两个正整数,第一个整数 M 代表研究材料的种类,第二个正整数 N,代表小明的行李空间。
第二行包含 M 个正整数,代表每种研究材料的所占空间。
第三行包含 M 个正整数,代表每种研究材料的价值。
输出描述
输出一个整数,代表小明能够携带的研究材料的最大价值。
输入示例
6 1
2 2 3 1 5 2
2 3 1 5 4 3
输出示例
5
提示信息
小明能够携带 6 种研究材料,但是行李空间只有 1,而占用空间为 1 的研究材料价值为 5,所以最终答案输出 5。
数据范围:
1 <= N <= 5000
1 <= M <= 5000
研究材料占用空间和价值都小于等于 1000
问题分析:动规5部曲
1. 定义数组dp,以及dp[i]的含义:
这里我们使用的是二位dp数组,那么dp[i][j]表示什么呢?i 代表的是0 到 i物品中任意取物品放到背包重量为j时的最大背包价值
2. 确定递推公式:
针对01背包,要么放与不放,如果放入物品的重量大于此时背包的重量,那么此时dp[i][j]的值该由什么更新呢?说明此时的背包只能有之前放入物品i的最大背包价值去更新,相当于,如果你把物品1放入此时的背包时,已经把背包装满,那么你在放入下一个物品的时候,背包是不是已经满了,那么背包的最大价值是不是由没有放入下一个物品时背包的最大价值更新,另外一种情况就是能入,但是你可以选择放入或者是不放入,对吧~,如果你放入与不放入相对比,那么背包的最大价值是否会变大呢?还是变小呢?这不能确定吧,在因为dp[i][j]表示的是,把物品i放入背包重量为j时的最大背包价值,所有要取不放入物品的价值和放入物品的价值取一个最大值,即:
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]), dp[i - 1][j - weight[i]] + value[i]这是什么意思呢?i为什么减一,而j不用呢,因为i - 1,是因为i(i最小值是1)表示的是物品,而初始化已经把一个物品的对应最大背包价值以及处理掉了,然后j为什么不减一呢,因为j的最小值也是1表示的是背包的重量,而重量为0的时候已经初始化掉了,然后 [j - weight] + value[i]什么意思呢,计算放入物品i的时候此时背包还剩下的重量对应的最大值在加上放入物品i的价值
3. 初始化dp数组:
一般是根据递推公式来初始化的,首先从dp[i][j]的定义出发,如果背包容量j为0的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为0。状态转移方程 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 可以看出i 是由 i-1 推导出来,那么i为0的时候就一定要初始化。如果不初始化,那么无法跟新dp[i][j]
dp[0][j],即:i为0,存放编号0的物品的时候,各个容量的背包所能存放的最大价值。
那么很明显当 j < weight[0]的时候,dp[0][j] 应该是 0,因为背包容量比编号0的物品重量还小。
当j >= weight[0]时,dp[0][j] 应该是value[0],因为背包容量放足够放编号0物品。
dp[0][j] 和 dp[i][0] 都已经初始化了,那么其他下标应该初始化多少呢?
其实从递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 可以看出dp[i][j] 是由左上方数值推导出来了,那么 其他下标初始为什么数值都可以,因为都会被覆盖
4. 遍历顺序:
先遍历背包重量,在遍历物品,或者反之,其实都是一样的,关键实在dp[i][j]的含义就行了,简单来说,无论是前者还是后者赋值都是一样的dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);因为要赋值的时候要与原来的dp数组含义相同。
5. 打印dp数组
以下这题的模版:
weight = [1, 3, 4] value = [15, 20, 30] bagweight = 4 # bagweight为什么要加一呢,因为背包重量可以0,也可以是bagweight dp = [[0] * (bagweight + 1) for _ in range(len(weight))] # 初始化 dp[i][j] 把物品i放入背包为j时的最大价值,最少只有一个物品的时候,那么就可以知道无论背包的重量为多少,那么它的最大价值都是这个物品的价值 for i in range(1, bagweight + 1): dp[0][i] = value[0] for i in range(1, len(weight)): # 遍历物品,因为物品1已经初始化调,所以从1开始 for j in range(1, bagweight + 1): # 遍历重量 if weight[i] > j: # 为什么不写weight[i - 1]呢,因为如i == 1时,i - 1 == 0,现在我们只能确定的是只放一个物品的价值 dp[i][j] = dp[i - 1][j] # 为什么写i-1呢,因为当前物品i的重量已经大于此时背包的重量,无法放入,只能有之前没有放入物品i的价值决定 # 假设背包重量为 1 的时候,初始化的时候,以及把物品为1重量为1放入此时的背包,然后,判断当前背包是否可以容纳第二个 # 物品的重量,如果不可以,说明此时背包重量为1的时候的最大值,只能有由放如物品的价值决定 else: # 如果此时能够放入,那么要考虑到,把这个物品i放入之后总的价值会变小还是会变大, # 那么就要计算,没有放入物品i是的价值,与放入物品i时价值加上放入物品i剩下背包重量的价值取一个最大值 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]) # print(dp[len(weight) - 1][bagweight])