Python题解—小明的背包

小明的背包

题目描述

小明有一个容量为 V 的背包。

这天他去商场购物,商场一共有 N 件物品,第 i 件物品的体积为 $w_i$,价值为 $v_i$。

小明想知道在购买的物品总体积不超过 V 的情况下所能获得的最大价值为多少,请你帮他算算。

输入描述

输入第 1 行包含两个正整数 N,V,表示商场物品的数量和小明的背包容量。

第 $2\sim N+1$ 行包含 2 个正整数 w,v,表示物品的体积和价值。

$1\leq N\leq10^2$,$1\leq V \leq 10^3$,$1 \leq w_i,v_i \leq10^3$。

输出描述

输出一行整数表示小明所能获得的最大价值。

样例输入

5 20
1 6
2 5
3 8
5 15
3 3

样例输出

37

问题分析:

​ 作为一个动态规划里最经典的问题。背包问题当然值得我们好好去研究。

​ 首先,我们已知什么?各物品的体积和价值、物品的数量以及背包的容积。那么要求什么呢?选定物品,然后使得在总体积保持规定范围内的情况下,总价值最大。所以对于该问题的第一步,就是选择物品。我们当然可以把每个物品选与不选看成一个变量,然后在满足条件的情况下穷尽,取出价值最大的那一个。很显然,我之前无数的建模失败都是基于此,因为当输入变为一个超大量的时候,穷举的时间复杂度是惊人的。所以,来想想怎么优化吧。

​ 穷举法的致命缺陷是,它并没有充分地利用之前运算过程中的信息。有些计算甚至可以说是大部分计算都是在浪费时间。比如我判断5个物品,在一定容积下的选择情况。如果我们知道判断4个物品,在一定容积下的最优选择情况。那么最优选择方法,一定是在对4个物品进行判断的基础上,对第5个物品进行判断。这样就避免了,前四个物品无头苍蝇般的选择。这就是动态规划法的核心。本题的矛盾点就在于前面物品的选择对后面物品的选择有借鉴意义。这就是动态规划。

拟定方案:

  1. 首先肯定是处理输入了。两个正整数就不谈了。对于各物品的体积和价值,我们采用数组来存储。其中数组的索引表示,物品的编号。这里提一个小技巧,我们完全可以把第0个索引忽略,这样逻辑就比较顺了。
  2. 动态规划问题最难的地方,肯定就是设计DP状态了(因为好的DP状态才能够进行状态转移)。所以本题的状态怎么设定呢?肯定要思考对于一个大问题的子问题核心矛盾点在哪里。一个是对前面物品的判断,这是解题的直接关键,另一个就是背包的最大容积,这是因为在状态转移的过程中(判断这个物品选不选的过程)需要知道较小的最大容积时的选择情况。所以设计DP状态为dp[i] [j],其中i指对前i个物品的判断,j表示此时的最大容积。对于这个状态,我们可以采用二维数组去存储。
  3. 第三步就是状态转移了。对于第i个物品的选择。一种情况,第i个物品的容积比要求的最大容积都大,那么它肯定不能选。第二种情况就是比最大容积要小,那么就有可能选。所以,这时我们就可以分别计算选和不选的价值是多少,然后取最大的。
  4. 第四步就是方程的编写了。我这里采用的是递推方法,(因为记忆化代码好难啊,而且DFS还没有看。。。)。递推方法肯定要限制一下递推的终点,因为如果不限制的话,它会一直走到宇宙尽头。同样我们可以用打表法,把所有的结果都写在一个二维数组上,然后从小到大往后推,进而可以得出整个数组,那么答案就很easy了。
  5. 优化:状态转移时其实只需要知道i-1的信息,所以我们在存储的时候,完全可以忽略前i-2行。
    1. 交替滚动法。因为只要知道两行嘛,我们完全可以就存两行,然后新的一行替代掉不需要的那一行,实现交替滚动。这个方法比较直接。
    2. 自我滚动法。这个方法就比较难了,其实对于存储的优化也仅仅是方法一的两倍而已(很多吗,额,方法一比原来的优化了n/2倍诶)。它是将所有的更新过程都放在一行中完成,首先每一个选择过程更新的都是容积大于等于volumes[i]的那部分(因为小于的那部分显然不可以更新),所以第二个for循环是在一定范围内的,而且要从后往前,从前往后的话会造成对后面的影响。这样第一个if又省去了。所以优化的本质是在尽可能小的空间内尽可能完成对不必要信息删去,然后实现完整的状态转移更新过程。
# 递推代码
n, v = map(int, input().split())

volumes = [0] # i表示第i件商品的体积
values = [0] # i表示第i件商品的价值

for i in range(n):
    volume, value = map(int, input().split())
    volumes.append(volume)
    values.append(value)
# 进行数据的输入,使用两个数组来存储数据

dp = [[0 for i in range(v + 1)] for j in range(n + 1)]
# i表示第i件商品选不选,j表示背包此状态下的容积,i件商品的容积之和不能大于它

for i in range(1, n + 1):
    for j in range(0, v + 1):
        # 当回溯的时候,是有可能为0的,所以要从0开始
        # 接下来就是判断选不选第i件商品
        if volumes[i] > j:
            dp[i][j] = dp[i - 1][j]
        else:
            dp[i][j] = max(dp[i - 1][j - volumes[i]] + values[i], dp[i - 1][j])
            # 对选还是不选做出比较

print(dp[n][v])

``` python
# 自我滚动的优化
n, v = map(int, input().split())

volumes = [0] # i表示第i件商品的体积
values = [0] # i表示第i件商品的价值

for i in range(n):
    volume, value = map(int, input().split())
    volumes.append(volume)
    values.append(value)
# 进行数据的输入,使用两个数组来存储数据

dp = [0 for i in range(v + 1)]
# i表示第i件商品选不选,j表示背包此状态下的容积,i件商品的容积之和不能大于它

for i in range(1, n + 1):
    for j in range(v, volumes[i] - 1, -1):
        dp[j] = max(dp[j - volumes[i]] + values[i], dp[j])
        # 对选还是不选做出比较

print(dp[v])
# 交替滚动的优化
n, v =  = [0] # i表示第i件商品的价值

for i in range(n):
    volume, value = map(int, input().split())
    volumes.append(volume)
    values.append(value)
# 进行数据的输入,使用两个数组来存储数据

dp = [第i件商品选不选,j表示背包此状态下的容积,i件商品的容积之和不能大于它
chif0
new = 1
for i in range(1, n + 1):
    old, new = new, old
    for j in range(0, v + 1):
        if volumes[i] > j:
            dp[new][j] = dp[old][j]
        else:
            dp[new][j] = max(dp[old][j - volumes[i]] + values[i], dp[old][j])
        # 对选还是不选做出比较

print(dp[new][v])

回顾反思:

  1. 小注意点:数组索引表示的信息符合逻辑是很不错的,没必要省那第一个位子的空间,除非你是一个牛人。
  2. 动态规划的本质是对于之前信息的充分利用。子问题对于后续问题要有帮助。
  3. 动态规划解题的流程:1. DP状态设计 2. 状态转移 3. 代码编写(递推或者记忆化) 4. 过程优化,删去不需要的存储信息。
  4. 对于状态的设计是至关重要的。
  5. 要小心,在局部就想着局部的细节,切记低级代码错误,找bug是很痛苦的。
posted @ 2022-03-09 23:50  潮自生  阅读(141)  评论(0)    收藏  举报