[python刷题模板] 背包问题
一、 算法&数据结构
1. 描述
0-1背包:题意给出n种物品分别的体积v和价值w,背包的总容量vol,每种物品只能选0或1个,问背包里最多能装多少价值。
完全背包:题意给出n种物品分别的体积v和价值w,背包的总容量vol,每种物品能选无限个,问背包里最多能装多少价值。
多重背包:题意给出n种物品分别的体积v、价值w和个数c,背包的总容量vol,每种物品能选c[i]个,问背包里最多能装多少价值。
混合背包:一道题里给出的物品分别可能是上述三种的情况,混合讨论,这种比较简单,分类转移即可,不多讨论,但模板是按这个写。
分组背包:题意给出n'组'物品,每组里有c种物品,每组只能选或不选1个物品,问背包里最多能装多少价值。
二维费用背包:一般是0-1背包的变种,给出的物品多了个重量,背包也有负重上限。简言之就是多了个维度。
以上所有问题还可以变成问方案数。
- 以上就是常见的几种背包题目,以下暂不讨论
二维费用背包
了,如果讨论会明确指出。 01背包
是最简单的背包入门,正常状态定义是二维:f = [[0]*(vol+1) for _ in range(n+1)] ,其中vol是背包容积,n是物品数。- 定义:f[i][j]为尝试了前i个物品,选的总体积为j时的最大价值。实现时一般把i+1偏移一位。
- 转移:考虑当前物品选或不选:不选f[i][j] = f[i-1][j];选f[i][j] = f[i-1][j-v]+w。
- 初始:f[0][0]=0,即一个不选价值是0(如果是问方案数则f[0][0]=1,表示一个不选的方案数只有1个)。
- 答案:若是问不超过背包容量则max(f[n]);若是问恰好容量j的情况则f[n][j]。
- 实现时,01背包可以倒序滚动优化,可以节省一维的空间,对于python这是巨大的性能提升,cf上有的题二维定义是过不了的。
- 在滚动写法下,
完全背包
只需在01背包的代码上修改j的转移顺序。 多重背包
比较进阶,但没那么难,除了单调队列优化外几乎可以看做01背包+完全背包的合体。分类讨论即可:- 若v*c>=vol,显然可以认为,对于这个包,这个物品是无限的,因为装满包也都用不完这个物品。
- 否则可以把这c个物品展开,一个一个遍历,跑c次01背包即可。但是复杂度就上升了
- 于是出现了多重背包的二进制优化和队列优化。其中二进制优化可以看做01背包做法的组合。
分组背包
和前三个背包联系就不那么紧密,和01背包还是有关系的(所以01背包是所有背包的基础):- 在01背包的基础上,最外层遍历组,每组选一个;那么组内先遍历体积j,每个j再遍历尝试组内的每个道具。
- 具体看代码和注释。
- 注意
多重背包
是无法求方案数的,除非同种物品可以区分。这一般会是分组背包
。 - 求方案数时,w价值一般是没用的,只需要考虑体积;且f[0][0]=1。
- 接上:多重背包和分组背包的区分,详细看我代码注释。
- 背包问题的话,acwing的前几道题全是模板可以试试。
- 用我的模板可以秒杀这几道题的大部分。
总结一下边界
- 求max/min的模型里:
- 求体积
恰好
为j: - 求max, f = [0]+[-inf]*t
- 求min, f = [0]+[inf]*t
- 最终f[j]代表体积恰好为j时的价值极值。
- 求体积
至多
为j时: - f[0] = [0]+[0]*t (max/min)
- 最终f[j]代表体积
至多
为j时的价值极值
- 求体积
至少
为j时: - f[0] = [0]+[0]*t (max/min)
- 同时遍历体积需要修改循环下界v->0、转移需要修改为从max(0,j-v),即
for j in range(self.vol, -1, -1):f[j] = merge(f[j], f[max(j - v,0)] + w) # 01背包
for j in range(self.vol+1):f[j] = merge(f[j], f[max(j - v,0)] + w) # 完全背包
- 最终f[j]代表体积
至少
为j时的价值极值
- 求体积
- 求方案数的模型里(一般要取模):
- 求体积
恰好
为j: - f = [1]+[0]*t
- 最终f[j]代表体积恰好为j时的方案数。
- 求体积
至多
为j时: - f = [1]+[1]*t
- 最终f[j]代表体积
至多
为j时的方案数。
- 求体积
至少
为j时: - f = [1]+[0]*t
- 同时遍历体积需要修改循环下界v->0、转移需要修改为从max(0,j-v),即
for j in range(self.vol, -1, -1):f[j] += f[max(j - v,0)] # 01背包
for j in range(self.vol+1):f[j] += f[max(j - v,0)] # 完全背包
- 最终f[j]代表体积
至多少
为j时的方案数
- 求体积
2. 复杂度分析
- 查询O(1)
- 更新update,O(nvolc)。
- 背包问题先看容量,如果容量过大考虑能否优化,比如变成>=的问题。
- 如果无法优化,就要考虑不要用背包做了。考虑其他的dp或者记忆化搜索。
3. 常见应用
- n个物品求方案数、求极值。
4. 常用优化
- 空间上可以滚动优化,一般用的比较多的是01背包的倒序枚举转移技巧。
- 多重背包优化是被提及最多的,可以把c那层优化成lg©或者1。但用的比较少。
二、 模板代码
0. 混合背包求最大/最小值模板(0-1/完全/多重)
例题: 7. 混合背包问题
- 使用时建议外部调用三种方法。
- 如果无法直接用考虑外部手写dp,毕竟不是很复杂。
- 注意很多dp不要强行靠背包。
# Problem: 混合背包问题
# Contest: AcWing
# URL: https://www.acwing.com/problem/content/7/
# Memory Limit: 64 MB
# Time Limit: 1000 ms
import sys
from collections import deque
from math import inf
RI = lambda: map(int, sys.stdin.buffer.readline().split())
RS = lambda: map(bytes.decode, sys.stdin.buffer.readline().strip().split())
RILST = lambda: list(RI())
DEBUG = lambda *x: sys.stderr.write(f'{str(x)}\n')
class PackMaxMin:
""" 背包问题求最大/最小值,复杂度O(体积*物品(种类)数),
如果是多重背包,可以优化成O(体积*物品种类数)
有两种使用方法:
1. 初始化只传入背包容量vol和极值方法merge(如:max)。外部遍历物品,手动调用p.zero_one_pack等方法。见solve1。
好处是不用思考怎么创建dp数组,怎么倒序遍历,遍历时的边界问题(如倒序:range(vol,v-1,-1))
2. 初始化同时传入对应类型的物品集合,然后调用run(),可以直接转移完成。但是需要在外部组合起需要的物品,比较麻烦。
好处是如果外部本身就组合过了,可以直接算,不用外部暴露逻辑。
无法处理的问题:
1. 求方案数(另写一个模板)
2. 求具体方案(还没想好)
3. 二维费用背包:一般是f定义成2维,然后倒序枚举j变成倒序枚举j、k即可,但对代码入侵性较强,变化也多,没想好怎么封装,有空可以尝试写一下试试。
"""
def __init__(self, vol, zero_one=None, multi=None, complete=None, merge=max, sit='just', ini=None):
"""关于ini:
如果求体积'恰好'j时,ini应该是-inf/inf
如果求体积'至少/至多'时,ini应该是0
考虑只有一个物品v=4,但枚举j==5的场景,
若初始化f[1]=0,f[5]会计算成f[1]+w,是有结果的,实际上认为5可以容纳4,即体积不超过/至多5的数据全部容纳。
若初始化f[1]=-inf,则f[5]计算也会是-inf,认为非法。则任何位置只能从0先转移一个合法数据。
"""
self.zero_one = zero_one # 用于01背包的物品 (v,w):体积,价值
self.multi = multi # 用于多重背包的物品 (v,w,c):体积,价值,数量
self.complete = complete # 用户完全背包的物品 (v,w):体积,价值
self.merge = merge # 取极值的方案数,一般是max
self.vol = vol # 背包容量,注意计算复杂度时要用到
if ini is None:
if sit == 'just':
ini = -inf if merge.__name__ == 'max' else inf
elif sit in ['at_least',
'at_most']: # 注意,至多的情况for循环需要修改为:for j in range(self.vol, -1, -1):f[j] = merge(f[j], f[max(j - v,0)] + w)
ini = 0
self.f = [0] + [ini] * vol # f[j]代表体积至多j时的最优值
def zero_one_pack(self, v, w):
""" 01背包,逆序处理即可滚动
v:体积, w:价值
"""
f, merge = self.f, self.merge
for j in range(self.vol, v - 1, -1):
f[j] = merge(f[j], f[j - v] + w)
def complete_pack(self, v, w):
""" 完全背包,正序处理即可滚动
v:体积, w:价值
"""
f, merge = self.f, self.merge
for j in range(v, self.vol + 1):
f[j] = merge(f[j], f[j - v] + w)
def multi_pack_by_zero_one(self, v, w, c):
""" 多重背包转化成01背包,效率最低,但思考方便,一些无脑dp的题可能可以参考到
注意和分组背包区分:外层枚举c个物品,内层倒序枚举j。因为每个物品都要尝试放,扫一遍j。
注意有时会和分组背包混淆:如题意描述成,第i种物品有c个,可以任选几个,但同种物品不区分。
这种情况用多重背包计算就会出现重复方案,实际上考虑分组背包:这组中有c种物品,只能选0/1个。这c种物品分别是1个i,2个i。。c个i。
v:体积, w:价值, c:本物品个数
"""
if v * c >= self.vol: # 如果数量足够到超过目标体积,那么可以当完全背包做,更快
return self.complete_pack(v, w)
for _ in range(c): # 直接展开,尝试c次即可
self.zero_one_pack(v, w)
def multi_pack_by_binary(self, v, w, c):
""" 多重背包的二进制优化,可以把复杂度里的*c变成*lg(c)。原理是:
所有数字都可以用1,2,4,8..等2的幂互相加起来,那么把c分解成这些数字分别尝试逆序转移即可,别忘记最后尝试剩余的部分
v:体积, w:价值, c:本物品个数
"""
if v * c >= self.vol:
return self.complete_pack(v, w)
f, merge = self.f, self.merge
k = 1
while k < c:
self.zero_one_pack(v * k, w * k)
c -= k
k <<= 1
self.zero_one_pack(v * c, w * c)
def multi_pack_by_mono_que(self, v, w, c):
""" 多重背包的单调队列优化,可以把复杂度里的*c变成*1。原理需要画图展开消项,比较复杂,还不是很懂。
v:体积, w:价值, c:本物品个数
"""
if v * c >= self.vol:
return self.complete_pack(v, w)
f, merge = self.f, self.merge
pre = f[:]
for k in range(v):
q = deque()
for j in range(k, self.vol + 1, v):
if q and q[0] < j - c * v:
q.popleft()
while q and pre[q[-1]] + (j - q[-1]) // v * w <= pre[j]:
q.pop()
q.append(j)
f[j] = pre[q[0]] + (j - q[0]) // v * w
def run(self):
"""直接计算这些背包的转移,除了很模板的题不建议使用"""
if self.zero_one:
for v, w in self.zero_one:
self.zero_one_pack(v, w)
if self.multi:
for v, w, c in self.multi:
self.multi_pack_by_mono_que(v, w, c)
if self.complete:
for v, w in self.complete:
self.complete_pack(v, w)
return self.f
# 1324 ms
def solve():
n, vol = RI()
pp = PackMaxMin(vol, merge=max)
for _ in range(n):
v, w, s = RI()
if s == 1 or s == -1:
pp.zero_one_pack(v, w)
elif s == 0:
pp.complete_pack(v, w)
else:
pp.multi_pack_by_mono_que(v, w, s) # 1324 ms
# pp.multi_pack_by_binary(v, w, s) # 1349 ms
# pp.multi_pack_by_zero_one(v, w, s) # 1353 ms
print(max(pp.f))
# 1342 ms
def solve1():
n, vol = RI()
zero_one, multi, complete = [], [], []
for _ in range(n):
v, w, s = RI()
if s == 1 or s == -1:
zero_one.append((v, w))
elif s == 0:
complete.append((v, w))
else:
multi.append((v, w, s))
pp = PackMaxMin(vol, zero_one=zero_one, multi=multi, complete=complete, merge=max)
print(max(pp.run()))
if __name__ == '__main__':
solve()
1. 分组背包求最大/最小值模板
例题: 9. 分组背包问题
- 分组背包和前边三种写一起感觉不太合适,不太可能放一起出题吧。
import sys
from math import inf
RI = lambda: map(int, sys.stdin.buffer.readline().split())
RS = lambda: map(bytes.decode, sys.stdin.buffer.readline().strip().split())
RILST = lambda: list(RI())
DEBUG = lambda *x: sys.stderr.write(f'{str(x)}\n')
class GroupedPackMaxMin:
"""分组背包求最大/最小值
传入多组物品,每组中只能选一个,求极值。转化成01背包考虑,外层枚举体积j,内层尝试这个j选不同物品(注意和多重背包区分)。
注意有时会和多重背包混淆:如题意描述成,第i种物品有c个,可以任选几个,但同种物品不区分。
这种情况用多重背包计算就会出现重复方案,实际上考虑分组背包:这组中有c种物品,只能选0/1个。这c种物品分别是1个i,2个i。。c个i。
"""
def __init__(self, vol, grouped_items=None, merge=max, sit='just', ini=None):
"""关于ini,可以不填,指定sit即可,其中sit用的最多的是at_most,这时一般可以直接返回p.f[-1]:
如果求体积'恰好'j时,ini应该是-inf/inf
如果求体积'至少/至多'时,ini应该是0
考虑只有一个物品v=4,但枚举j==5的场景,
若初始化f[1]=0,f[5]会计算成f[1]+w,是有结果的,实际上认为5可以容纳4,即体积不超过/至多5的数据全部容纳。
若初始化f[1]=-inf,则f[5]计算也会是-inf,认为非法。则任何位置只能从0先转移一个合法数据。
"""
self.grouped_items = grouped_items # 形如[[(1,2),(2,3)],[(1,2),(2,3)],],注意是多组数组,每组中只能选一个
self.merge = merge
self.vol = vol
if ini is None:
if sit == 'just':
ini = -inf if merge.__name__ == 'max' else inf
elif sit in ['at_least',
'at_most']: # 注意,至多的情况for循环需要修改为:for j in range(self.vol, -1, -1):f[j] = merge(f[j], f[max(j - v,0)] + w)
ini = 0
self.f = [0] + [ini] * vol # f[j]代表体积j时的最优值,
def grouped_pack(self, items):
f, merge = self.f, self.merge
for j in range(self.vol, 0, -1):
for v, w in items:
if j >= v:
f[j] = merge(f[j], f[j - v] + w)
def run(self):
if self.grouped_items:
for items in self.grouped_items:
self.grouped_pack(items)
return self.f
# 1065 ms
def solve():
n, vol = RI()
gp = GroupedPackMaxMin(vol)
for _ in range(n):
s, = RI()
a = []
for _ in range(s):
a.append(RILST())
gp.grouped_pack(a)
print(max(gp.f))
# 1071 ms
def solve1():
n, vol = RI()
items = []
for _ in range(n):
s, = RI()
a = []
for _ in range(s):
a.append(RILST())
items.append(a)
gp = GroupedPackMaxMin(vol, items)
print(max(gp.run()))
if __name__ == '__main__':
solve()
2. 01背包求方案数模板(完全背包也在,但没测)
class PackCountPlan:
""" 背包问题求方案数,复杂度O(体积*物品(种类)数),一般求方案数很大,都要取模,这里为了防止忘记取模/模写错,直接调用全局变量,如果忘记定义会报错。
注意和求极值不同:
1. dp[0][0] = 1代表 一个不取的方案数是1
2. 数组里只需要一列体积参数,不需要价值。
一般是01背包,完全背包。可能无法处理多重背包,因为无法区分同种类物品。这时尝试考虑分组背包,把每种物品看成一组,这组里的物品分别是1个i,2个i..c个i。
"""
def __init__(self, vol, zero_one=None, multi=None, complete=None,sit='just',ini=0):
"""关于ini
如果是求体积'恰好'为j时的方案数,ini=0;
如果是求体积'至多'为j时的方案数,ini=1;
如果是求体积'至少'为j时的方案数,ini=1; 同时遍历体积需要改动为 for j in range(self.vol, v - 1, -1):f[j] = (f[j] + f[max(j - v,0)]) % MOD
"""
self.zero_one = zero_one # 用于01背包的物品 (v):体积
self.multi = multi # 用于多重背包的物品 (v,c):体积,数量--- 注意用不了,我只是没删,可能后续想办法补。
self.complete = complete # 用户完全背包的物品 (v):体积
self.vol = vol # 背包容量,注意计算复杂度时要用到
if ini is None:
if sit == 'just':
ini = 0
elif sit in ['at_least',
'at_most']: # 注意,至多的情况for循环需要修改为:for j in range(self.vol, -1, -1):f[j] = merge(f[j], f[max(j - v,0)] + w)
ini = 1
self.f = [1] + [ini] * vol # f[j]代表体积j时的方案,
def zero_one_pack(self, v):
""" 01背包,逆序处理即可滚动
v:体积
"""
f = self.f
for j in range(self.vol, v - 1, -1):
f[j] = (f[j] + f[j - v]) % MOD
def complete_pack(self, v, w):
""" 完全背包,正序处理即可滚动
v:体积, w:价值
"""
f = self.f
for j in range(v, self.vol + 1):
f[j] = (f[j] + f[j - v]) % MOD
def run(self):
"""直接计算这些背包的转移,除了很模板的题不建议使用"""
if self.zero_one:
for v in self.zero_one:
self.zero_one_pack(v)
if self.complete:
for v in self.complete:
self.complete_pack(v)
return self.f
3. 分组背包求方案数
例题: 6310. 获得分数的方法数
- 比赛前没学过分组背包,赛中一直以为是多重背包,结果不会去重,做不出来。
- 这里整理成模板。
- 本题注意是分组背包,因为同一种物品无法区分,相当于每种物品是一组,组内可以选任意一种物品(k个物品的组合。)
MOD = 10**9+7
class GroupedPackCountPlan:
""" 分组背包问题求方案数,复杂度O(体积*物品(种类)数),一般求方案数很大,都要取模,这里为了防止忘记取模/模写错,直接调用全局变量,如果忘记定义会报错。
注意和求极值不同:
1. dp[0][0] = 1代表 一个不取的方案数是1
2. 数组里只需要一列体积参数,不需要价值。
一般是01背包,完全背包。可能无法处理多重背包,因为无法区分同种类物品。这时尝试考虑分组背包,把每种物品看成一组,这组里的物品分别是1个i,2个i..c个i。
"""
def __init__(self, vol, grouped_items=None):
self.grouped_items = grouped_items # 用于01背包的物品 (v):体积
self.vol = vol # 背包容量,注意计算复杂度时要用到
self.f = [1] + [0] * vol # f[j]代表体积j时的方案,如果是求min这里初始值要改成inf
def grouped_pack(self, items): # 注意传进来的是本组的物品的体积们:[1,6,2,3,4,5..],最好是排序的可以优化一下break
f = self.f
for j in range(self.vol, 0, -1): # 注意外层循环遍历体积j,内层尝试放组内每个物品。
for v in items:
if j >= v: # 这里可以尝试sort break,但是最好能在外层预处理或者天然是排序的。
f[j] = (f[j]+f[j - v] )%MOD
def run(self):
"""直接计算这些背包的转移,除了很模板的题不建议使用"""
if self.grouped_items:
for v in self.grouped_items:
self.grouped_pack(items)
return self.f
class Solution:
def waysToReachTarget(self, target: int, types: List[List[int]]) -> int:
gp = GroupedPackCountPlan(target)
for count,marks in types:
items = [] # 本组物品的体积相当于尝试用k个marks,其中k<=count
for i in range(1,count+1):
items.append(marks*i)
gp.grouped_pack(items) # 整理好本组物品再一起传进去
return gp.f[-1] % MOD
4. 01背包求最优选择的方案数(双dp数组,同时计算)
例题: 11. 背包问题求方案数
- 题意是找最优方案的方案数,可能是在不同体积上。而在这个体积上也不一定能求出最优方案。
- 因此要一边算最优一边算方案数。
- 用两个数组:f[j]代表体积j时,最优价值;g[j]代表体积j时,最优价值的方案数。g=[1]+[0]*v
- 另一种写法是:f不变;g[j]代表体积不超过j时,最优价值的方案数。区别是初始化时g全1:g=[1]*(vol+1)
import sys
RI = lambda: map(int, sys.stdin.buffer.readline().split())
RS = lambda: map(bytes.decode, sys.stdin.buffer.readline().strip().split())
RILST = lambda: list(RI())
DEBUG = lambda *x: sys.stderr.write(f'{str(x)}\n')
MOD = 10 ** 9 + 7
# ms
def solve1():
n, vol = RI()
f = [0] + [0] * vol
g = [1] + [0] * vol # g[j]是体积恰好j时最优价值的方案数,这样需要计算最优方案数的话,需要找到每个最优的j相加。
for _ in range(n):
v, w = RI()
for j in range(vol, v - 1, -1):
if f[j] < f[j - v] + w:
f[j] = f[j - v] + w
g[j] = g[j - v]
elif f[j] == f[j - v] + w:
g[j] += g[j - v]
g[j] %= MOD
mx = max(f)
ans = 0
for i, v in enumerate(f):
if mx == v:
ans += g[i]
print(g[-1] % MOD)
def solve():
n, vol = RI()
f = [0] + [0] * vol
g = [1] + [1] * vol # g[j]是体积不超过j时最优解的方案数,最优方案数的话,就可以直接返回cnt[-1]
for _ in range(n):
v, w = RI()
for j in range(vol, v - 1, -1):
if f[j] < f[j - v] + w:
f[j] = f[j - v] + w
g[j] = g[j - v]
elif f[j] == f[j - v] + w:
g[j] += g[j - v]
g[j] %= MOD
print(g[-1] % MOD)
if __name__ == '__main__':
solve()
5. 有依赖的背包(树形依赖) acw10. 有依赖的背包问题
例题: 10. 有依赖的背包问题
- 树形DP和背包的结合。
- 由于必须选父节点才能向下选,因此下边节点选的时候,体积会缩小,需要自己定义dp数组处理。
- 同时把每个子树可能组合的方案计算出来,那么每颗子树就是一个分组背包的组。
- 用这些子树作为一个分组背包更新当前节点即可。
- 其实相当于做了n次分组背包。时间复杂度(O(NVV))
- 由于py dfs的限制,每个分组方便返回自己的状态的,但是还好同时只能存在一个返回的dp数组,可以用一个全局变量储存。
# Problem: 有依赖的背包问题
# Contest: AcWing
# URL: https://www.acwing.com/problem/content/10/
# Memory Limit: 64 MB
# Time Limit: 1000 ms
import sys
from types import GeneratorType
from math import inf
RI = lambda: map(int, sys.stdin.buffer.readline().split())
def bootstrap(f, stack=[]):
def wrappedfunc(*args, **kwargs):
if stack:
return f(*args, **kwargs)
else:
to = f(*args, **kwargs)
while True:
if type(to) is GeneratorType:
stack.append(to)
to = next(to)
else:
stack.pop()
if not stack:
break
to = stack[-1].send(to)
return to
return wrappedfunc
# ms
def solve():
n, vol = RI()
g = [[] for _ in range(n)] # 建图
a = []
root = 0
for i in range(n):
v, w, p = RI()
if p == -1:
root = i
else:
g[p - 1].append(i)
a.append((v, w)) # 体积和价值
p = [] # 一个全局dp数组,记录每棵子树dfs后,子树产生的背包dp数组长啥样。(所有组合的可能性),后序遍历时他就是分组背包的物品。
@bootstrap
def dfs(u, s): # 尝试放u节点,s代表还能用多少空间(因为之前选择依赖项必须占用一定空间
v, w = a[u] # 当前节点体积和价值
f = [-inf] * (s + 1) # 当前的空间数组
if v <= s: # 如果自己能放上,那就放上,不放不让选子树。
f[v] = w
vv = v # 记录一下体积,后边要使用v变量当邻居(我的习惯
for v in g[u]:
yield dfs(v, s - vv) # 后序遍历,先计算子树的背包数组,用子树状态更新当前节点;由于放了本节点,剩余体积要-vv
for j in range(s, 0, -1): # 倒序更新f,子树能产生的状态是个分组背包,因此先遍历体积,选择子树状态其中的一个。
for v, w in enumerate(p): # 遍历子树状态
if v <= j: # 能放上
f[j] = max(f[j], f[j - v] + w)
p[:] = f[:]
yield
dfs(root, vol)
print(max(p))
if __name__ == '__main__':
solve()
6. 转化为01背包
例题: LCP 47. 入场安检
- 如果有x个房间,他们都转为栈模式,那么只要他们先进去共k个人,且每个房间都剩个尾巴,那所有房间就都变成了队列,第k个人就正好可以第一个出来了。
- 因此转化为01背包,容量是k,物品是房间,价值是1,体积是cap[i]-1。问多少种组合能满足体积是k。
class Solution:
def securityCheck(self, capacities: List[int], k: int) -> int:
MOD = 1000000007
n = len(capacities)
# m = len(capacities[-1])
# 房间是物品,k是容量
f = [0]*(k+1)
f[0] = 1
for v in capacities:
v -= 1
for j in range(k,v-1,-1):
f[j] = (f[j]+f[j-v])%MOD
return f[-1]
三、其他
- 多重背包的单调队列优化暂时不会,先复制。
- 二维费用背包的后续在写。