Count of Sub-Multisets With Bounded Sum

Count of Sub-Multisets With Bounded Sum

You are given a 0-indexed array nums of non-negative integers, and two integers l and r.

Return the count of sub-multisets within nums where the sum of elements in each subset falls within the inclusive range of [l, r].

Since the answer may be large, return it modulo 10+ 7.

sub-multiset is an unordered collection of elements of the array in which a given value x can occur 0, 1, ..., occ[x] times, where occ[x] is the number of occurrences of x in the array.

Note that:

  • Two sub-multisets are the same if sorting both sub-multisets results in identical multisets.
  • The sum of an empty multiset is 0.

 

Example 1:

Input: nums = [1,2,2,3], l = 6, r = 6
Output: 1
Explanation: The only subset of nums that has a sum of 6 is {1, 2, 3}.

Example 2:

Input: nums = [2,1,4,2,7], l = 1, r = 5
Output: 7
Explanation: The subsets of nums that have a sum within the range [1, 5] are {1}, {2}, {4}, {2, 2}, {1, 2}, {1, 4}, and {1, 2, 2}.

Example 3:

Input: nums = [1,2,1,3,5,2], l = 3, r = 5
Output: 9
Explanation: The subsets of nums that have a sum within the range [3, 5] are {3}, {5}, {1, 2}, {1, 3}, {2, 2}, {2, 3}, {1, 1, 2}, {1, 1, 3}, and {1, 2, 2}.

 

Constraints:

  • 1 <= nums.length <= 2 * 104
  • 0 <= nums[i] <= 2 * 104
  • Sum of nums does not exceed 2 * 104.
  • 0 <= l <= r <= 2 * 104
 

解题思路

  昨晚大概可以分析出是单调队列优化多重背包的做法,不过太久没做忘了。

  我们可以将原问题转换成求集合元素之和不超过 r 的集合数量 dp(r),以及集合元素之和不超过 l1 的集合数量 dp(l1),那么 dp(r)dp(l1) 就是集合元素之和确切在 [l,r] 范围内的集合数量了。

  为此我们就可以用动态规划进行求解 dp(m),定义状态 f(i,j) 表示从前 i 个元素中选出总和不超过 j 的所有方案的数量。根据是否选择第 i 个数来划分状态,因此有状态转移方程f(i,j)=f(i1,j)+f(i1,jwi)

  很明显时间复杂度是 O(nm)。但题目中还有个约束条件 i=1nwi2104,假设原序列中不同的数最多有 k 个,意味着有 k(k+1)22104k<200。因此可以对原序列按照值来进行分组,那么最多会被分成 200 组,问题就变成了分组背包的问题。如果直接用朴素的多重背包来求解的话,时间复杂度还是 O(nm)。假设 k 是分组的数量,那么用单调队列优化后时间复杂度就优化到了 O(km)

  问题转换成多重背包后状态定义有所改变,f(i,j) 表示从前 i 组中选出总和不超过 j 的所有方案的数量,根据选择第 i 组的元素 wi 的数量进行状态划分,有状态转移方程f(i,j)=k=0swif(i1,jkwi)

  把式子展开,并发现之间的关联性:

f(i,j)=f(i1,j)+f(i1,jwi)+f(i1,j2wi)++f(i1,jswiwi)f(i,jwi)=f(i1,jwi)+f(i1,j2wi)++f(i1,jswiwi)+f(i1,j(swi+1)wi)f(i,j2wi)=f(i1,jwi)+f(i1,j2wi)++f(i1,jswiwi)+f(i1,j(swi+1)wi)+f(i1,j(swi+2)wi)f(i,j(jwi1)wi)=f(i,j(jwi1)wi)+f(i1,jjwiwi)f(i,jjwiwi)=f(i1,jjwiwi)

  当固定 wi 后,对于所有可能的 j[0,m],根据 jwi 的结果 r=jmodwi=jjwiwi 进行分类,那么就可以分成 0wi1 这么多类。对于固定的 r,从 r 开始遍历,依次计算 f(i,r),f(i,r+wi),,并在这个过程中维护一个大小不超过 swi+1 的滑动窗口以及窗口内元素的和,对于那么对于 f(i,r+kwi) 其结果就是窗口内元素的和(即对应的状态转移方程)。

  还有个细节就是不能有 wi=0 的情况,否则 f(i,j) 就可以转移到 f(i,j) 本身,就会形成环不能 dp 了。由于 0 对总和没有影响,所以我们先跳过 wi=0 的情况,最后计算答案时再把含 0 的情况考虑上,因此 dp(m) 最终的答案就是 f(k,m)×(s0+1)。 

  AC 代码如下,时间复杂度为 O(km)

class Solution {
public:
    int mod = 1e9 + 7;
    
    int countSubMultisets(vector<int>& nums, int l, int r) {
        sort(nums.begin(), nums.end());
        vector<int> cnt(nums.back() + 1);
        for (auto &x : nums) {  // 统计每个值的个数
            cnt[x]++;
        }
        nums.erase(unique(nums.begin(), nums.end()), nums.end());   // 去重分组
        if (!nums[0]) nums.erase(nums.begin()); // 不考虑0的情况
        int n = nums.size();    // 分组的数量
        function<int(int)> dp = [&](int m) {
            if (m < 0) return 0;
            vector<vector<int>> f(n + 1, vector<int>(m + 1));
            for (int i = 0; i <= m; i++) {
                f[0][i] = 1;
            }
            for (int i = 1; i <= n; i++) {
                int w = nums[i - 1];
                for (int j = 0; j < w; j++) {
                    int s = 0;
                    for (int k = j; k <= m; k += w) {
                        if (k - (cnt[w] + 1) * w >= 0) s = (s - f[i - 1][k - (cnt[w] + 1) * w] + mod) % mod;    // 超出窗口大小
                        s = (s + f[i - 1][k]) % mod;
                        f[i][k] = s;
                    }
                }
            }
            int ret = (f[n][m] * (cnt[0] + 1ll)) % mod; // 再把0的情况考虑进来
            return ret;
        };
        return (dp(r) - dp(l - 1) + mod) % mod;
    }
};

 

参考资料

  第 115 场力扣夜喵双周赛:https://leetcode.cn/circle/discuss/maICM3/view/zXw3Ko/

  力扣第115场双周赛:https://www.bilibili.com/video/BV1H34y1g7k7/

posted @   onlyblues  阅读(43)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
历史上的今天:
2022-10-15 F. Multi-Colored Segments
Web Analytics
点击右上角即可分享
微信分享提示