0


【动态规划】【同余前缀和】【多重背包】[推荐]2902. 和带限制的子多重集合的数目

本文涉及知识点

动态规划汇总
C++算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频
C++算法:滑动窗口总结
多重背包

LeetCode2902. 和带限制的子多重集合的数目

给你一个下标从 0 开始的非负整数数组 nums 和两个整数 l 和 r 。
请你返回 nums 中子多重集合的和在闭区间 [l, r] 之间的 子多重集合的数目 。
由于答案可能很大,请你将答案对 109 + 7 取余后返回。
子多重集合 指的是从数组中选出一些元素构成的 无序 集合,每个元素 x 出现的次数可以是 0, 1, …, occ[x] 次,其中 occ[x] 是元素 x 在数组中的出现次数。
注意:
如果两个子多重集合中的元素排序后一模一样,那么它们两个是相同的 子多重集合 。
空 集合的和是 0 。
示例 1:
输入:nums = [1,2,2,3], l = 6, r = 6
输出:1
解释:唯一和为 6 的子集合是 {1, 2, 3} 。
示例 2:
输入:nums = [2,1,4,2,7], l = 1, r = 5
输出:7
解释:和在闭区间 [1, 5] 之间的子多重集合为 {1} ,{2} ,{4} ,{2, 2} ,{1, 2} ,{1, 4} 和 {1, 2, 2} 。
示例 3:
输入:nums = [1,2,1,3,5,2], l = 3, r = 5
输出:9
解释:和在闭区间 [3, 5] 之间的子多重集合为 {3} ,{5} ,{1, 2} ,{1, 3} ,{2, 2} ,{2, 3} ,{1, 1, 2} ,{1, 1, 3} 和 {1, 2, 2} 。
提示:
1 <= nums.length <= 2 * 104
0 <= nums[i] <= 2 * 104
nums 的和不超过 2 * 104 。
0 <= l <= r <= 2 * 104

动态规划

vCnt[i]记录i在nums中出现的次数,vCnt[i]不为0的数目不超过200个。
子多重集合 就是子序列。
i为0要特殊处理,否则会死循环。

动态规划的状态表示

dp[i][j] 表示 ,从[0,i]中选取若干个数和为j的可能数。状态数:O(200r)。
注意用滚动向量vPre、dp实现。
由于unorder_map 大约是O(10),所以有超时的风险。直接vector<vector<>> 空间复杂度是:O(nr),空间会超。

利用前缀和优化转移方程

计算后置状态:
dp[j] =

       ∑ 
      
      
      
        x 
       
      
        : 
       
      
        0 
       
      
      
      
        v 
       
      
        C 
       
      
        n 
       
      
        t 
       
      
        [ 
       
      
        i 
       
      
        ] 
       
      
     
    
      v 
     
    
      P 
     
    
      r 
     
    
      e 
     
    
      [ 
     
    
      j 
     
    
      − 
     
    
      x 
     
    
      × 
     
    
      i 
     
    
      ] 
     
     
    
      s 
     
    
      . 
     
    
      t 
     
     
    
      j 
     
    
      − 
     
    
      x 
     
    
      × 
     
    
      i 
     
    
      > 
     
    
      = 
     
    
      0 
     
    
   
  
    \Large\sum_{x:0}^{vCnt[i]}vPre[j-x\times i] \quad s.t \quad j-x \times i>=0 
   
  
∑x:0vCnt[i]​vPre[j−x×i]s.tj−x×i>=0

显然,可以用前缀和优化。
转移方程的时间复杂度为:O(1),总时间复杂度为O(200r)。

动态规划的填表顺序

i从大到小。从小到大似乎也没问题。

动态规划的初始值

vPre[0]=1

动态规划的范围值

       ∑ 
      
      
      
        x 
       
      
        : 
       
      
        l 
       
      
     
       r 
      
     
    
      v 
     
    
      P 
     
    
      r 
     
    
      e 
     
    
      [ 
     
    
      x 
     
    
      ] 
     
    
   
  
    \Large \sum _{x:l}^r vPre[x] 
   
  
∑x:lr​vPre[x]

代码

核心代码

template<int MOD =1000000007>classC1097Int{public:C1097Int(longlong llData =0):m_iData(llData% MOD){}
    C1097Int  operator+(const C1097Int& o)const{returnC1097Int(((longlong)m_iData + o.m_iData)% MOD);}
    C1097Int&operator+=(const C1097Int& o){
        m_iData =((longlong)m_iData + o.m_iData)% MOD;return*this;}
    C1097Int&operator-=(const C1097Int& o){
        m_iData =(m_iData + MOD - o.m_iData)% MOD;return*this;}
    C1097Int  operator-(const C1097Int& o){returnC1097Int((m_iData + MOD - o.m_iData)% MOD);}
    C1097Int  operator*(const C1097Int& o)const{return((longlong)m_iData * o.m_iData)% MOD;}
    C1097Int&operator*=(const C1097Int& o){
        m_iData =((longlong)m_iData * o.m_iData)% MOD;return*this;}booloperator<(const C1097Int& o)const{return m_iData < o.m_iData;}
    C1097Int pow(longlong n)const{
        C1097Int iRet =1, iCur =*this;while(n){if(n &1){
                iRet *= iCur;}
            iCur *= iCur;
            n >>=1;}return iRet;}
    C1097Int PowNegative1()const{returnpow(MOD -2);}intToInt()const{return m_iData;}private:int m_iData =0;;};classSolution{public:intcountSubMultisets(vector<int>& nums,int left,int r){constint iMax =*std::max_element(nums.begin(), nums.end());
        vector<int>vCnt(1+ iMax);for(constauto& n : nums){
            vCnt[n]++;}
        vector<C1097Int<>>vPre(r +1);
        vPre[0]=1;for(int i = iMax; i >=0; i--){if(0== vCnt[i]){continue;}
            vector<C1097Int<>>dp(r +1);if(0== i){for(int k =0; k <= r; k++){
                    dp[k]= vPre[k]*(1+ vCnt[i]);}}else{for(int m =0; m < i; m++){
                    C1097Int<> iiSum =0;for(int k = m; k <= r; k += i){
                        iiSum += vPre[k];constint delIndex = k -(vCnt[i]+1)* i;if(delIndex >=0){
                            iiSum -= vPre[delIndex];}
                        dp[k]= iiSum;}}}
            vPre.swap(dp);}
        C1097Int<> biRet = std::accumulate( vPre.begin()+ left, vPre.begin()+ r +1,C1097Int<>());return biRet.ToInt();}};

测试用例

emplate<classT,classT2>voidAssert(const T& t1,const T2& t2){assert(t1 == t2);}template<classT>voidAssert(const vector<T>& v1,const vector<T>& v2){if(v1.size()!= v2.size()){assert(false);return;}for(int i =0; i < v1.size(); i++){Assert(v1[i], v2[i]);}}intmain(){
    vector<int> nums;int l,  r;{
        Solution sln;
        nums ={1,2,2,3}, l =6, r =6;auto res = sln.countSubMultisets(nums, l, r);Assert(1, res);}{
        Solution sln;
        nums ={2,1,4,2,7}, l =1, r =5;auto res = sln.countSubMultisets(nums, l, r);Assert(7, res);}{
        Solution sln;
        nums ={1,2,1,3,5,2}, l =3, r =5;auto res = sln.countSubMultisets(nums, l, r);Assert(9, res);}}

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771

如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

相关

下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653
我想对大家说的话闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。如果程序是一条龙,那算法就是他的是睛

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

标签: 动态规划 算法 c++

本文转载自: https://blog.csdn.net/he_zhidan/article/details/136828606
版权归原作者 闻缺陷则喜何志丹 所有, 如有侵权,请联系我们删除。

“【动态规划】【同余前缀和】【多重背包】[推荐]2902. 和带限制的子多重集合的数目”的评论:

还没有评论