蓝桥杯python笔记(4-3):背包问题——01背包

一、01背包

给定一个容积为V的背包,现在有N件物品,第i件物品的体积为wi,价值为vi。每件物品只能拿或者不拿,请求出体积总和不超过V的最大价值。

  • 贪心策略可以吗?每次选择性价比最高的。
  • V=6,三件物品,体积分别为1,3,5,价值分别为10,20,30。按照性价比肯定是优先选择体积为1的物品,然后选择体积为3,总价值为30。实际上选择体积为1、5的两件才是最优的。

给定一个容积为V的背包,现在有N件物品,第i件物品的体积为wi,价值为vi。每件物品只能拿或者不拿,请求出体积总和不超过V的最大价值。

动态规划状态:dp[i][j]:前i件物品,体积为j的最大价值。 dp[...][0]=dp[0][...]=0

对于第i件物品,仅考虑两种情况:

  • 拿:dp[i][j]从dp[i-1][j-wi]转移过来
  • 不拿:dp[i][j]从dp[i-1][j]转移过来

dp[i][j] = \begin{cases} dp[i - 1][j] & \text{if } j < w[i] \\ \max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]) & \text{if } j \geq w[i] \end{cases}

例题 P1174 小明的背包


题目描述

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

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

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


输入描述

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

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

1≤N≤10^2,1≤V≤10^3,1≤wi,vi≤10^3。

输出描述

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

N,V=map(int,input().split())
dp=[[0]*(V+1) for i in range(N+1)]

# dp[i][j]表示前i件物品,体积不超过j的最大价值
for i in range(1,N+1):
    wi,vi=map(int,input().split())
    for j in range(0,V+1):
        if j < wi:
            dp[i][j]=dp[i-1][j]
        else:
            dp[i][j]=max(dp[i-1][j],dp[i-1][j-wi]+vi)
print(dp[N][V])

 

二、滚动数组优化

给定一个容积为V的背包,现在有N件物品,第i件物品的体积为wi,价值为vi。每件物品只能拿或者不拿,请求出体积总和不超过V的最大价值。

dp[i][j] = \begin{cases} dp[i - 1][j] & \text{if } j < w[i] \\ \max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]) & \text{if } j \geq w[i] \end{cases}

上述转移方程中,第i行的更新,仅和第i-1行有关系,因此可以使用滚动数组。

长度为2的滚动数组: 

N,V=map(int,input().split())
dp=[[0]*(V+1) for i in range(2)]
#滚动数组优化
for i in range(1,N+1):
    wi,vi=map(int,input().split())
    for j in range(0,V+1):
        if j < wi:
            dp[i%2][j]=dp[(i-1)%2][j]
        else:
            dp[i%2][j]=max(dp[(i-1)%2][j],dp[(i-1)%2][j-wi]+vi)
print(dp[N%2][V])

 

 使用长度为1的滚动数组优化空间:

dp[j] =\begin{cases}dp[j] & j < w[i] \\max(dp[j], dp[j - w[i]] + v[i]) & j \geq w[i] \end{cases}

 
 
​i−1,j−w[i]​...​i−1,j​
​i,j​
  • 更新dp[i][j]时,用到的是上一行对应位置(dp[i - 1][j])和上一行先前位置(dp[i - 1][j - w[i]])的元素,因此可以使用单个数组进行更新:直接从大到小对dp数组进行覆盖即可。
  • ​dp[j]=max(dp[j],dp[j−w[i]]+v[i])​
  • 从大到小:这样才能保证每次使用到的是上一层的值

长度为1的滚动数组:

n, V = map(int, input().split())   
dp = [0] * (V+1)
   
for i in range(1, n + 1):
    w, v = map(int, input().split())
    #必须从大到小
    for j in range(V, w - 1, -1):
        dp[j] = max(dp[j], dp[j - w] + v)   
print(dp[V])

三、例题 P2223 背包与魔法*

问题描述

小蓝面前有 N 件物品, 其中第 i 件重量是 Wi​, 价值是 Vi 。她还有一个背包, 最大承重是 M 。

小蓝想知道在背包称重范围内, 她最多能装总价值多少的物品?

特别值得一提的是, 小蓝可以使用一个魔法 (总共使用一次), 将一件物品 的重量增加 K, 同时价值秝倍。(当然小蓝也可以不使用魔法)


输入格式

第一行包含 3 个整数 N、M和 K 。

以下 N 行, 每行两个整数 Wi 和 Vi 。

输出格式

一个整数代表答案。

 

  • 和01背包基本相同,多了一个使用魔法的维度。
  • 因此状态增加一维,dp[i][j][k]表示前i件物品,重量为j,使用魔法次数为k次的最大价值。
  • 对于第i件物品,存在三种选项:选、不选、选并且使用魔法。
    • dp[i][j][k]=max(dp[i - 1][j][k], dp[i - 1][j - w[i]][k]+v[i])
    • or k == 1: dp[i - 1][j - w[i] - K][0]+2v[i] 。

 

N, M, K = map(int, input().split())
# 滚动数组dp[i][j][k]表示前i件物品,重量为j,魔法次数为k的最大价值
# dp[i][j][k] = max(dp[i - 1][j][k], dp[i - 1][j - w][k] + v)
# if k == 1: dp[i][j][k] = max(dp[i][j][k], dp[i - 1][j - w - K][0] + 2 * v)
dp = [[[0] * 2 for j in range(M + 1)] for i in range(N + 1)]
for i in range(1, N + 1):
    w, v = map(int, input().split())
    for j in range(M + 1):
        for k in range(2):
            if j < w:
                dp[i][j][k] = dp[i-1][j][k]
            else:
                dp[i][j][k] = max(dp[i-1][j][k], dp[i-1][j-w][k]+v)
            if k == 1 and j >= w + K:
                dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-w-K][0]+2*v)
    # print(dp[i])
print(max(dp[N][M][0], dp[N][M][1]))
N, M, K = map(int, input().split())
# 滚动数组dp[j][k]表示前i件物品, 重量为j, 魔法次数为k的最大价值
# dp[j][k] = max(dp[j][k], dp[j - w][k] + v)
# if k == 1: dp[j][k] = max(dp[j][k], dp[j - w - K][0] + 2 * v)
dp = [[0] * 2 for j in range(M + 1)]
for i in range(1, N + 1):
    w, v = map(int, input().split())
    for j in range(M, w - 1, -1):
        dp[j][0] = max(dp[j][0], dp[j - w][0] + v)
        dp[j][1] = max(dp[j][1], dp[j - w][1] + v)
        if j >= w + K:
            dp[j][1] = max(dp[j][1], dp[j - w - K][0] + 2 * v)
    # print(dp)
print(max(dp[M][0], dp[M][1]))
特性代码1:三维数组代码2:二维滚动数组
空间复杂度较高:O(N * M * 2),因为使用了三维数组存储所有状态。较低:O(M * 2),使用二维数组滚动更新,节省空间。
时间复杂度较高:O(N * M * 2),因为需要遍历三维数组。相同:O(N * M * 2),但实际运行效率更高,因为减少了内存访问开销。
更新方式每次更新时,直接覆盖dp[i][j][k]的值。从大到小更新dp[j][k],避免覆盖未使用的上一行数据。
代码复杂度较高:需要显式处理i维度的更新逻辑。较低:通过从大到小更新,隐式处理了i维度的滚动逻辑。
适用场景适合初学者理解动态规划的状态转移过程。适合优化空间复杂度,适合大规模数据场景。
输出结果结果存储在dp[N][M][0]dp[N][M][1]中,取最大值。结果存储在dp[M][0]dp[M][1]中,取最大值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值