算法基础——动态规划(3)背包 多重背包(每个物品可重复选,有次数限制)

算法基础——动态规划(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]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值