有 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,...,N−1。
定义状态
: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[i−1][j],f[i−1][j−v[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,...,N−1,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[i−1][j],f[i−1][j−v[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,...,−inf,−inf,...,−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,...,N−1,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[i−1][j−k∗v[i]]+k∗w[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])
转换的等价性证明
:由于第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])
刚刚那个问题,我们是延续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,...,N−1,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[i−1][j−k∗v[i]]+k∗w[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])