动态规划——背包问题(2)

多重背包的单调队列优化

例题

有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V (0<N≤1000, 0<V≤20000),用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N≤1000
0<V≤20000
0<vi,wi,si≤20000
提示
本题考查多重背包的单调队列优化方法。
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10

思路

多重背包的原始状态转移方程
f ( i , j ) = m a x ( f ( i − 1 , j ) , f ( i − 1 , j − v ) + w , ⋯ , f ( i − 1 , j − s v ) + s w ) f(i,j)=max(f(i−1,j),f(i−1,j−v)+w,⋯,f(i−1,j−sv)+sw) f(i,j)=max(f(i1,j),f(i1,jv)+w,,f(i1,jsv)+sw)此时s是满足 j − s ∗ v > = 0 j-s*v>=0 jsv>=0的最大值
考虑用完全背包的优化方式来优化这个方程
f ( i , j − v ) = m a x ( f ( i − 1 , j − v ) , f ( i − 1 , j − 2 v ) + w , ⋯ , f ( i − 1 , j − ( s + 1 ) v ) + ( s ) w ) f(i,j−v)=max(f(i−1,j−v),f(i−1,j−2v)+w,⋯,f(i−1,j−(s+1)v)+(s)w) f(i,jv)=max(f(i1,jv),f(i1,j2v)+w,,f(i1,j(s+1)v)+(s)w)这里的s为每样物品的最大个数。
此公式并不能推出f(i, j)
继续推导
在这里插入图片描述
其中 r = j   m o d   v i r=j\ mod\ v_i r=j mod vi,也可以理解为 完全背包 下把当前物品 选到不能再选 后,剩下的 余数
得到 f ( i , r ) = f ( i − 1 , r ) f(i,r)=f(i−1,r) f(i,r)=f(i1,r) 后,我们再利用 完全背包优化思路 往回倒推一遍会惊奇的发现一个 滑动窗口求最大值的模型

	f(i, r)  =    f(i-1, r)
	f(i, r + v) = max(f(i - 1, r + v) - w, f(i - 1, r)) + w
	f(i, r + 2 * v) = max(f(i - 1, r + 2 * v) - 2 * w, f(i - 1, r + v)) - w, f(i - 1, r)) + 2 * w
	...
	f(i, j) = max(f(i - 1, j) - s * w, f(i - 1, j - v) - (s - 1) * w, ... , f(i - 1, j - sv)) + s * w

可以发现每次只需要获得至多窗口值小于等于s的上一层的最大值即可
在这里插入图片描述

代码
N = 20010
q, f, g = [0] * N, [0] * N, [0] * N

n, m = map(int, input().split())

for i in range(n) :
	v, w, s = map(int, input().split())
	g = f[:]
	for j in range(v) :
		hh, tt = 0, -1
		for k in range(j, m + 1, v) :
			if hh <= tt and q[hh] < k - s * v :
				hh += 1
			while hh <= tt and g[q[tt]] - (q[tt] - j) // v * w <= g[k] - (k - j) // v * w :
				tt -= 1
			tt += 1
			q[tt] = k
			f[k] = g[q[hh]] + (k - q[hh]) // v * w
print(f[m])

二维费用背包问题

例题

有 N 件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。

每件物品只能用一次。体积是 vi,重量是 mi,价值是 wi。

求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。

输入格式
第一行三个整数,N,V,M,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。

接下来有 N 行,每行三个整数 vi,mi,wi,用空格隔开,分别表示第 i 件物品的体积、重量和价值。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N≤1000
0<V,M≤100
0<vi,mi≤100
0<wi≤1000
输入样例
4 5 6
1 2 3
2 4 4
3 4 5
4 5 6
输出样例:
8
在这里插入图片描述

N = 110
f = [[0] * N for _ in range(N)]

n, V, M = map(int, input().split())
for i in range(n) :
	v, m, w = map(int, input().split())
	for j in range(V, v - 1, -1):
		for k in range(M, m - 1, -1) :
			f[j][k] = max(f[j][k], f[j - v][k - m] + w)
print(f[V][M]) 

背包问题装法的总结:至多、恰好、至少

假设求价值最多的情况

背包最多装V体积

状态表示f[i, j]:前i个物品中选择体积不大于j的选法
初始化:当一个物品都没选时,f[0, j] = 0
循环体积时,当vi>j时,这个物品是不能选的,所以范围是[vi, V]

背包恰好装V体积

状态表示f[i, j]:前i个物品中选择体积恰好为j的选法
初始化:当一个物品都没选时,只有f[0, 0]是有意义的为0,其它的没有意义(f[0, j], j!=0)相应的初始化为INF
循环体积时,当vi>j时,这个物品是不能选的,所以范围是[vi, V]

背包最少装V体积

状态表示f[i, j]:前i个物品中选择体积至少为j的选法
初始化:当一个物品没选时,只有f[0, 0]是有意义的为0,其它的没有意义(f[0, j], j!=0)相应的初始化为INF
循环体积时,当vi>j时,这个物品是可以选的,所以范围是[0, V],可以特判if vi > j : f[i, j] = max(f[i - 1, j], wi),也可以做统一化处理if v > j : f[i, j] = max(f[i - 1, j],f[i -1, max(0, j - vi)] + wi)

例题

潜水员为了潜水要使用特殊的装备。

他有一个带2种气体的气缸:一个为氧气,一个为氮气。

让潜水员下潜的深度需要各种数量的氧和氮。

潜水员有一定数量的气缸。

每个气缸都有重量和气体容量。

潜水员为了完成他的工作需要特定数量的氧和氮。

他完成工作所需气缸的总重的最低限度的是多少?

例如:潜水员有5个气缸。每行三个数字为:氧,氮的(升)量和气缸的重量:

3 36 120

10 25 129

5 50 250

1 45 130

4 20 119
如果潜水员需要5升的氧和60升的氮则总重最小为249(1,2或者4,5号气缸)。

你的任务就是计算潜水员为了完成他的工作需要的气缸的重量的最低值。

输入格式
第一行有2个整数 m,n。它们表示氧,氮各自需要的量。

第二行为整数 k 表示气缸的个数。

此后的 k 行,每行包括ai,bi,ci,3个整数。这些各自是:第 i 个气缸里的氧和氮的容量及气缸重量。

输出格式
仅一行包含一个整数,为潜水员完成工作所需的气缸的重量总和的最低值。

数据范围
1≤m≤21,
1≤n≤79,
1≤k≤1000,
1≤ai≤21,
1≤bi≤79,
1≤ci≤800
输入样例:
5 60
5
3 36 120
10 25 129
5 50 250
1 45 130
4 20 119
输出样例:
249

M, N = 22, 80
INF = int(1e9)

f = [[INF] * N for _ in range(N)]
f[0][0] = 0
m, n = map(int, input().split())

K = int(input())
for k in range(K) :
	a, b, c = map(int, input().split())
	for i in range(m, -1, -1) :
		for j in range(n, -1, -1) :
			f[i][j] = min(f[i][j], f[max(i - a, 0)][max(j - b, 0)] + c) 
print(f[m][n])

求解方案数 & 判断是否存在

初始化和循环顺序

状态表示f[i, j]:表示前i个物品选取体积满足至多/恰好/至少为j的方案数

初始化:f[0, 0] = 1满足状态表示意义,当j!=0时f[0, j],在体积至多为j时,方案数为1,即f[0, j] = 1。恰好为j时,方案数为0,即f[0, j] = 0。在体积至少为j时,方案数为i即f[0, j] = 0。

组合数:先循环物品再循环体积
排列数,先循环体积再循环物品
状态转移方程
0-1背包f[i, j] += f[i - 1, j - vi]
完全背包f[i, j] += f[i, j - vi]
多重背包f[i, j] += f[i, j - vi * k]

多重背包求方案数时,特别注意当当前物品选取数量为0时,f[i][j] += f[i - 1][j]。然而当用滚动数组优化时f[j] += f[j]会将f[i - 1][j]加两遍,需要特判,当不选时的方案已经继承到了第i层,不需要操作。

是否存在问题可以转化为方案数问题

例题

给定 N 个正整数 A1,A2,…,AN,从中选出若干个数,使它们的和为 M,求有多少种选择方案。

输入格式
第一行包含两个整数 N 和 M。

第二行包含 N 个整数,表示 A1,A2,…,AN。

输出格式
包含一个整数,表示可选方案数。

数据范围
1≤N≤100,
1≤M≤10000,
1≤Ai≤1000,
答案保证在 int 范围内。

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

0-1背包求(组合)方案数,恰好装满

N = 10010
f = [0] * N

n, m = map(int, input().split())

nums = list(map(int, input().split()))
f[0] = 1
for i in range(n) :
    for j in range(m, nums[i] - 1, -1) :
        f[j] += f[j - nums[i]]
        
print(f[m])

小明手里有n元钱全部用来买书,书的价格为10元,20元,50元,100元。

问小明有多少种买书方案?(每种书可购买多本)

输入格式
一个整数 n,代表总共钱数。

输出格式
一个整数,代表选择方案种数。

数据范围
0≤n≤1000
输入样例1:
20
输出样例1:
2
输入样例2:
15
输出样例2:
0
输入样例3:
0
输出样例3:
1

完全背包求(组合)方案数,恰好装满

N = 1010
f = [0] * N

money = [10, 20, 50, 100]

m = int(input())

f[0] = 1
for v in money :
    for j in range(v, m + 1) :
        f[j] += f[j - v]
        
print(f[m])
    

求解具体方案

思路

在这里插入图片描述

f[i, j] = max(f[i - 1, j], f[i - 1, j - v] + w),f[i, j]只能由以上两个状态中得出。
分为三种情况:在这里插入图片描述

考虑方案按字典顺序输出
在这里插入图片描述

例题

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

输出 字典序最小的方案。这里的字典序是指:所选物品的编号所构成的序列。物品的编号范围是 1…N。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式
输出一行,包含若干个用空格隔开的整数,表示最优解中所选物品的编号序列,且该编号序列的字典序最小。

物品编号范围是 1…N。

数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 6
输出样例:
1 4

N = 1010
f = [[0] * N for _ in range(N)]
v, w = [0] * N, [0] * N

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

for i in range(n, 0, -1) :
	for j in range(m + 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])

j = m
for i in range(1, n + 1) :
	if j >= v[i] and f[i][j] == f[i + 1][j - v[i]] + w[i] :
		print(i, end = " ")
		j -= v[i]

总结

到此为止,背包问题第二部分就讲完了,涉及到了多重背包单调队列优化、不同背包问法的背包初始化和循环顺序问题、以及求方案数和具体方案的过程
求具体方案时背包不能使用滚动数组
在不使用滚动数组的情况下,0-1背包和分组背包要特写不选物品的情况即f[i, j] = f[i - 1, j],其他背包则包含在物品选取个数的逻辑中。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值