01,完全,多重背包,背包问题(Python)

文章目录

  • 概述
  • 01-背包问题
    • 题目描述:
    • 分析
      • 原分析
      • 扩展分析
    • 代码
  • 完全背包
    • 题目描述
    • 分析
    • 代码
      • 原分析代码
      • 二维dp转一维dp代码
      • 省略取物品次数k的等价转换代码
  • 多重背包
    • 题目描述
    • 分析
    • 代码

概述

  1. 01-背包问题
  2. 完全背包问题
  3. 多重背包问题

01-背包问题

题目描述:

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。第 i 件物品的体积是 v i v_i vi,价值是 w i w_i wi。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。

输入样例
4 5
1 2
2 4
3 4
4 5

输出样例:
8

分析

原分析

N 件物品 , 背包容量 V, 每件只能使用一次
数组存入物品的体积和价值 :体积 v [ i ] v[i] v[i], 价值 w [ i ] w[i] w[i] i = 0 , 1 , 2 , . . . , N − 1 i=0,1,2,...,N-1 i=0,1,2,...,N1

定义状态f[i][j], 代表前i个物品,存入容量为j的背包里的最大价值

状态转移 f [ i ] [ j ] = max ⁡ ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − v [ i ] ] + w [ i ] ) f[i][j] = \max{(f[i-1][j],f[i-1][j-v[i]] + w[i])} f[i][j]=max(f[i1][j],f[i1][jv[i]]+w[i])

  1. 不取第i个物品: f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j] = f[i-1][j] f[i][j]=f[i1][j]
  2. 取第i个物品: f [ i ] [ j ] = f [ i − 1 ] [ j − v [ i ] ] + w [ i ] f[i][j] = f[i-1][j-v[i]] + w[i] f[i][j]=f[i1][jv[i]]+w[i]

边界 f [ 0 ] = [ 0 , 0 , 0... , 0 ] f[0] = [0,0,0...,0] f[0]=[0,0,0...,0]

result = f[N][V]

状态转移表格

N|V 0 1 2 3 4 5
0 0 0 0 0 0 0
1 0 2 2 2 2 2
2 0 2 4 6 6 6
3 0 2 4 6 6 8
4 0 2 4 6 6 8

扩展分析

扩展题目要求:求解将哪些物品装入背包,可使这些物品的总体积刚好等于背包容量,且总价值最大。输出最大价值。

N 件物品 , 背包容量 V, 每件只能使用一次
数组存入物品的体积和价值 :体积 v [ i ] v[i] v[i], 价值 w [ i ] w[i] w[i] i = 1 , 2 , . . . , N − 1 , N i=1,2,...,N-1,N i=1,2,...,N1,N

定义状态f[i][j], 代表前i个物品,物品的总体积恰好为j且存入容量为j的背包里的最大价值

状态转移 f [ i ] [ j ] = max ⁡ ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − v [ i ] ] + w [ i ] ) f[i][j] = \max{(f[i-1][j],f[i-1][j-v[i]] + w[i])} f[i][j]=max(f[i1][j],f[i1][jv[i]]+w[i])

  1. 不取第i个物品: f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j] = f[i-1][j] f[i][j]=f[i1][j]
  2. 取第i个物品: f [ i ] [ j ] = f [ i − 1 ] [ j − v [ i ] ] + w [ i ] f[i][j] = f[i-1][j-v[i]] + w[i] f[i][j]=f[i1][jv[i]]+w[i]

边界 f [ 0 ] = [ 0 , − i n f , . . . , − i n f , − i n f , . . . , − i n f ] f[0] = [0,-inf,...,-inf,-inf,...,-inf] f[0]=[0,inf,...,infinf,...,inf]

result = f[N][V]

状态转移表格

N|V 0 1 2 3 4 5
0 -inf -inf -inf -inf -inf -inf
1 0 2 -inf -inf -inf -inf
2 0 2 4 6 -inf -inf
3 0 2 4 6 6 8
4 0 2 4 6 6 8

在扩展问题的条件下,相对于原问题的分析只改变了状态的定义规则,以及初始条件,但在编码层次上,只改变了初始条件。

在扩展问题的分析下,原问题的解为:
res = max{f[N][0,1,2,...,V]}

代码

N, V = map(int, input().split())  # 物品数, 背包容量

v = [0] * (N + 1)  # 体积 索引从1开始到n
w = [0] * (N + 1)  # 价值 索引从1开始到n

for i in range(1, N + 1):
    v[i], w[i] = map(int, input().split())

f = [[0 for i in range(V+1)] for i in range(N+1)]  # 初始化全0

for i in range(1, N + 1):
    for j in range(V + 1):
        f[i][j] = f[i - 1][j]

        if j >= v[i]:
            f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i])


print(f[N][V])

优化:从代码和状态转移公式都能看出来当前层的状态只与上一层状态有关,可以优化dp二维列表f为一维列表,从后往前更新确保更新第i层用的是第i-1层的状态值

N, V = map(int, input().split())  # 物品数, 背包容量

v = [0] * (N + 1)  # 体积 索引从1开始到n
w = [0] * (N + 1)  # 价值 索引从1开始到n

for i in range(1, N + 1):
    v[i], w[i] = map(int, input().split())

f = [0 for i in range(V+1)]  # 初始化全0

for i in range(1, N + 1):
    for j in range(V,v[i]-1,-1):
            f[j] = max(f[j], f[j - v[i]] + w[i])

print(f[V])

完全背包

题目描述

有 N 件物品和一个容量是 V 的背包。每件物品使用次数不限。第 i 件物品的体积是 v i v_i vi,价值是 w i w_i wi。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。

输入样例
4 5
1 2
2 4
3 4
4 5

输出样例:
10

分析

N 件物品 , 背包容量 V, 每件使用次数不限
数组存入物品的体积和价值 :体积 v [ i ] v[i] v[i], 价值 w [ i ] w[i] w[i] i = 0 , 1 , 2 , . . . , N − 1 , N i=0,1,2,...,N-1,N i=0,1,2,...,N1,N

定义状态f[i][j], 代表前i个物品,存入容量为j的背包里的最大价值

状态转移 f [ i ] [ j ] = max ⁡ ( f [ i − 1 ] [ j − k ∗ v [ i ] ] + k ∗ w [ i ] ) , i = 0 , 1 , . . . , j / / v [ i ] 。 f[i][j] = \max{(f[i-1][j-k*v[i]] + k*w[i])}, i=0,1,...,j//v[i]。 f[i][j]=max(f[i1][jkv[i]]+kw[i]),i=0,1,...,j//v[i]
k的范围代表,第i个物品取的次数。上限不能超过当前背包的容量。

边界 f [ 0 ] = [ 0 , 0 , 0... , 0 ] f[0] = [0,0,0...,0] f[0]=[0,0,0...,0]

result = f[N][V]

状态转移表格

N|V 0 1 2 3 4 5
0 0 0 0 0 0 0
1 0 2 4 6 8 10
2 0 2 4 6 8 10
3 0 2 4 6 8 10
4 0 2 4 6 8 10

最终代码可以将二维的dp等价转换为一维dp,并且有省略物品次数k的等价转换。在下面代码部分转述并证明其等价性。

扩展分析可类比01背包问题。

代码

原分析代码

N, V = map(int, input().split())

v = [0] * (N + 1)
w = [0] * (N + 1)

for i in range(1, N + 1):
    v[i], w[i] = map(int, input().split())


f = [[0 for i in range(V+1)] for i in range(N+1)]  # 初始化全0

for i in range(1, N + 1):
    for j in range(V + 1):
        f[i][j] = f[i - 1][j]
        for k in range(1, j // v[i] + 1):
            f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i])

print(f[N][V])

二维dp转一维dp代码

转换的等价性证明:由于第i层状态只与第i-1层状态有关,从后往前更新确保更新第i层用的是第i-1层的状态值

N, V = map(int, input().split())

v = [0] * (N + 1)
w = [0] * (N + 1)

for i in range(1, N + 1):
    v[i], w[i] = map(int, input().split())

f = [0 for i in range(V+1)]  # 初始化全0

for i in range(1, N + 1):
    for j in range(V, v[i]-1, -1):
        for k in range(0, j // v[i] + 1):
            f[j] = max(f[j], f[j - k * v[i]] + k * w[i])

print(f[V])

省略取物品次数k的等价转换代码

刚刚那个问题,我们是延续01背包的问题,从后往前递推。但是对于这个问题,其实可以通过从前往后递推。

假设在考虑第i个物品时的两个状态:
A:dp[k*v[i] + x]
B:dp[(k-1)*v[i] + x]

根据前面的归纳,从前一个状态递推过来:

要求A的值,应该要从k+1个状态中选出最大的:

dp[x] + k*w[i]
dp[v[i] + x] + (k-1)*w[i]
dp[2*v[i] + x] + (k-2)*w[i]
...
...
dp[(k-1)*v[i] + x] + w[i]
dp[k*v[i] + x] 

要求B的值,应该要从k个状态中选出最大的:

dp[x] + (k-1)*w[i]
dp[v[i] + x] + (k-2)*w[i]
dp[2*v[i] + x] + (k-3)*w[i]
...
...
dp[(k-2)*v[i] + x] + w[i]
dp[(k-1)*v[i] + x]

我们可以看到,一一对应过来的话,这两个状态实际上只差一个w[i]的值。因此:A:dp[k*v[i] + x]= B:dp[(k-1)*v[i] + x] + w[i]

一方面我们可以根据前一个状态(i-1)推出此时的状态,另一方面由于当前状态前面的值也是当前问题的子问题,因此我们也可以从前面的值推到后面的值。

N, V = map(int, input().split())

v = [0] * (N + 1)
w = [0] * (N + 1)

for i in range(1, N + 1):
    v[i], w[i] = map(int, input().split())

f = [0 for i in range(V+1)]  # 初始化全0

for i in range(1, N + 1):
    for j in range(v[i], V+1):
            f[j] = max(f[j], f[j-v[i]] + w[i])

print(f[V])

多重背包

题目描述

有 N 种物品和一个容量是 V 的背包。第 i 种物品最多有 s i s_i si 件,每件体积是 v i v_i vi,价值是 w i w_i wi。求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。输出最大价值。

输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10

分析

N 件物品 , 背包容量 V, 每件使用次数 s i s_i si次。
数组存入物品的体积和价值和使用次数 :体积 v [ i ] v[i] v[i], 价值 w [ i ] w[i] w[i],使用次数 s [ i ] s[i] s[i], i = 0 , 1 , 2 , . . . , N − 1 , N i=0,1,2,...,N-1,N i=0,1,2,...,N1,N

定义状态f[i][j], 代表前i个物品,存入容量为j的背包里的最大价值

状态转移 f [ i ] [ j ] = max ⁡ ( f [ i − 1 ] [ j − k ∗ v [ i ] ] + k ∗ w [ i ] ) , k = 0 , 1 , . . . , min ⁡ ( s i , j / / v [ i ] ) 。 f[i][j] = \max{(f[i-1][j-k*v[i]] + k*w[i])}, k=0,1,...,\min(s_i, j//v[i])。 f[i][j]=max(f[i1][jkv[i]]+kw[i]),k=0,1,...,min(si,j//v[i])
k的范围代表,第i个物品取的次数。上限不能超过当前背包的容量,也不能超过题目限制的使用次数。

边界 f [ 0 ] = [ 0 , 0 , 0... , 0 ] f[0] = [0,0,0...,0] f[0]=[0,0,0...,0]

result = f[N][V]

状态转移表格

N|V 0 1 2 3 4 5
0 0 0 0 0 0 0
1 0 2 4 6 6 6
2 0 2 4 6 8 10
3 0 2 4 6 8 10
4 0 2 4 6 8 10

最终代码可以将二维的dp等价转换为一维dp。

扩展分析可类比01背包问题。

代码

N,V = map(int, input().split())

v = [0] * (N+1)
w = [0] * (N+1)
s = [0] * (N+1)

for i in range(1, N+1):
    v[i], w[i], s[i] = map(int, input().split())
    
    
f = [0] *(V+1)

for i in range(1, N+1):
    for j in range(V,v[i]-1,-1):
        for k in range(1, min(s[i], j//v[i])+1):
            f[j] = max(f[j], f[j-k*v[i]]+k*w[i])
            
print(f[V])

你可能感兴趣的:(数据结构与算法)