算法基础——动态规划(3)背包 多重背包(每个物品可重复选,有次数限制)
2585. 获得分数的方法数 1910
与完全背包不同的点在,首先,其次,空间优化成一个数组时,完全背包只需要当前target的上一组状态,而多重背包需要当前target及其之前的数的上一组状态。这决定了我们需要由后向前更新
class Solution:
def waysToReachTarget(self, target: int, types: List[List[int]]) -> int:
d=[1]+[0]*target
for x in types:
for c in range(target,x[1]-1,-1):
for j in range(1,min(c//x[1],x[0])+1):
d[c]+=d[c-x[1]*j]
return d[-1]%(10**9+7)
2902. 和带限制的子多重集合的数目 2759
Counter()函数可以直接统计每个值出现次数生成字典。对于字典不能用enumerate,得用dict.items()来获得键和值。
本题对于时间优化的要求极为严苛,如果用常规多重背包,是过不了的(尽管下方代码已经做了一个小优化,让时间近乎减半)。
class Solution:
def countSubMultisets(self, nums: List[int], l: int, r: int) -> int:
d_nums=Counter(nums)
d=[d_nums[0]+1]+[0]*r
del d_nums[0]
label=0
for x,c in d_nums.items():
label=min(label+x*c,r)# 优化1:计算当前最大sum值
for i in range(label,x-1,-1):
for j in range(1,min(i//x,c)+1):
d[i]+=d[i-x*j]
d[i]%=10**9+7
return sum(d[_] for _ in range(l,r+1))%(10**9+7)
优化方法一:(滚动数组)寻找递推关系,缩减重复计算
这种方法无论从数组前到后还是后到前,都不能避免同时使用到修改前和修改后的值,所以需要复制一个d记录数组。
class Solution:
def countSubMultisets(self, nums: List[int], l: int, r: int) -> int:
d_nums=Counter(nums)
d=[d_nums[0]+1]+[0]*r
del d_nums[0] # 重点1:key=0的处理
label=0
for x,c in d_nums.items():
d_new=d.copy()# 重点2:本方法需要复制记录数组
label=min(label+x*c,r) # 优化1:计算当前最大sum值
for i in range(x,label+1):
#优化2:用递推关系来计算,减少一个for循环
d_new[i]+=d_new[i-x]
if i>=(c+1)*x:# 重点3:判断数值为i时递推关系存不存在这一项,存在则需要减去
d_new[i]-=d[i-(c+1)*x]
d_new[i]%=10**9+7
d=d_new
return sum(d[_] for _ in range(l,r+1))%(10**9+7)
优化方法二:(称作同余前缀和法)但感觉也是一种递推,只不过是在原数组上进行。
class Solution:
def countSubMultisets(self, nums: List[int], l: int, r: int) -> int:
MOD = 10 ** 9 + 7
cnt = Counter(nums)
f = [cnt[0] + 1] + [0] * r
del cnt[0]
s = 0
for x, c in cnt.items():
s = min(s + x * c, r)
for j in range(x, s + 1):
f[j] = (f[j] + f[j - x]) % MOD # 原地计算同余前缀和
t = (c + 1) * x
for j in range(s, t - 1, -1):
f[j] = (f[j] - f[j - t]) % MOD # 两个同余前缀和的差
return sum(f[l:]) % MOD
有大神想出了由优化一变化到优化二的思考
优化二有更简单的理解,建立在优化一的基础之上,
本质就是优化一,只是代码上的trick把滚动数组去掉了
先倒序遍历:
f[j]=f[j]−f[j−(c[x]+1)∗x]
由于是倒序遍历,此时的f[j−(c[x]+1)∗x]还未被更新,
依旧是上一轮的旧值,对应原方程中的f[i−1][j−(c[x]+1)∗x]
再正序遍历:
f[j]=f[j]+f[j−x]
此时的f[j−x]已经在前面更新过,
是本轮的新值,对应原方程中的f[i][j−x]
277

被折叠的 条评论
为什么被折叠?



