【算法刷题】动态规划Ⅰ——“0-1背包”

0-1背包问题

每样物品只有一件,如何使得在限定的金额(以及物品数量)中,购买效用最大的物品组合;
附件必须跟随主件购买,无法单独购买。

购物单

输入:

50 5
20 3 5
20 3 5
10 3 0
10 2 0
10 1 0

单价价格:

编号主件附件1附件2
310
410
5102020

价值:

价格*满意度

编号主件附件1附件2
330
420
5106060

核心思路:建立动态规划表格

状态转移数组
  • 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[i1][j],dp[i1][jw[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种基本操作情况:

  1. 主件不投放
  2. 主件投放

状态转移方程
对于每个(有2附件的)主件,其自身状态有以下4种

由于最多有2个附件

  1. 主件投放
  2. 主件投放+附件1
  3. 主件投放+附件2
  4. 主件投放+附件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])
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值