0-1背包问题
每样物品只有一件,如何使得在限定的金额(以及物品数量)中,购买效用最大的物品组合;
附件必须跟随主件购买,无法单独购买。
购物单
输入:
50 5
20 3 5
20 3 5
10 3 0
10 2 0
10 1 0
单价价格:
编号 | 主件 | 附件1 | 附件2 |
---|---|---|---|
3 | 10 | ||
4 | 10 | ||
5 | 10 | 20 | 20 |
价值:
价格*满意度
编号 | 主件 | 附件1 | 附件2 |
---|---|---|---|
3 | 30 | ||
4 | 20 | ||
5 | 10 | 60 | 60 |
核心思路:建立动态规划表格
状态转移数组
d[i][j]
,前i
个物品,背包重量为j
的情况下的最大价值(此处,j
相当于所面临的重量约束)- d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − w [ i ] ] + v [ i ] ) dp[i][j]=max(dp[i-1][j], dp[i-1][j-w[i]] + v[i]) dp[i][j]=max(dp[i−1][j],dp[i−1][j−w[i]]+v[i])
普通的背包问题
每个物品只有两种选择:放 or 不放
dp = [[0]*(n+1) for _ in range(m+1)]
# 遍历各个物品
for i in range(1, m+1):
# 遍历背包的不同重量(<= m)
for j in range(1, n+1):
# 如果背包目前的承载力>当前物品的负重
if j - w[i] >= 0:
dp[i][j] = max(dp[i-1][j], dp[i][j-w[i]]+v[i])
else:
dp[i][j] = dp[i-1][j]
return dp[m][n]
- 为什么
n+1
?——因为可能所有物品都不购买,比实际物品数量+1 - 为什么
m+1
?——重量为闭区间[0,m]
购物车问题
列:主件m
行:总金额N
dp = [[0]*(m+1) for _ in range(N+1)]
对于每个主件,有以下2种基本操作情况:
- 主件不投放
- 主件投放
状态转移方程
对于每个(有2附件的)主件,其自身状态有以下4种:
由于最多有2个附件
- 主件投放
- 主件投放+附件1
- 主件投放+附件2
- 主件投放+附件1+附件2
因此,dp[i][j] = max(物品不放入背包,主件,主件+附件1,主件+附件2,主件+附件1+附件2)
。
dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i][k]]+v[i][k])
- 第
i
个物品以4种情况中价值最大的状态,要么放入背包,要么不放入背包。【0-1问题】 - 在当前问题中,有3个主件。
w[i][k]
:第i
个主件的第k
个状态, k ∈ { 0 , 1 , 2 , 3 } k\in\{0,1,2,3\} k∈{0,1,2,3}
dp = [[0]*(n+1) for _ in range(m+1)]
# m: 第i个物品放入情况
for i in range(1, m+1):
# n: 背包的内存
for j in range(1, n+1):
max_i = dp[i-1][j]
for k in range(len(w[j])):
if j - w[i][k] >= 0:
max_i = max(max_i, dp[i-1][j-w[i][k]]+v[i][k])
dp[i][j] = max_i
完整代码
n, m = map(int, input().split())
# 存储主件、附件的信息/数据
primary, annex = {}, {}
for i in range(1, m+1):
x, y, z = map(int, input().split())
# 主件
if z == 0:
primary[i] = [x, y]
# 附件
else:
if z in annex:
annex[z].append([x,y])
else:
annex[z] = [[x,y]]
# 主件个数
m = len(primary)
dp = [[0]*(n+1) for _ in range(m+1)]
# 占位,从而保证在之后的循环中,与循环指示符对应
w, v = [[]], [[]]
for key in primary:
w_temp, v_temp = [], []
w_temp.append(primary[key][0])
v_temp.append(primary[key][0]*primary[key][1])
# 存在附件
if key in annex:
# 主件+附件1(1个附件)
w_temp.append(w_temp[0]+annex[key][0][0])
v_temp.append(v_temp[0]+annex[key][0][0]*annex[key][0][1])
# 两个附件
if len(annex[key]) > 1:
# 主件+附件2
w_temp.append(w_temp[0]+annex[key][1][0])
v_temp.append(v_temp[0]+annex[key][1][0]*annex[key][1][1])
# 主件+附件1+附件2
w_temp.append(w_temp[0]+annex[key][0][0]+annex[key][1][0])
v_temp.append(v_temp[0]+annex[key][0][0]*annex[key][0][1]+annex[key][1][0]*annex[key][1][1])
w.append(w_temp)
v.append(v_temp)
# 状态转移方程
# 遍历主件个数和背包容量
for i in range(1, m+1):
# 物品的价格是10的整数倍
for j in range(10, n+1, 10):
max_i = dp[i-1][j]
for k in range(len(w[i])):
if j - w[i][k] >= 0:
max_i = max(max_i, dp[i-1][j-w[i][k]]+v[i][k])
dp[i][j] = max_i
print(dp[m][n])