2902.和带限制的子多重集合的数目
题目描述:
给你一个下标从 0 开始的非负整数数组 nums
和两个整数 l
和 r
。
请你返回 nums
中子多重集合的和在闭区间 [l, r]
之间的 子多重集合的数目 。
由于答案可能很大,请你将答案对 $10^9 + 7$ 取余后返回。
子多重集合 指的是从数组中选出一些元素构成的 无序 集合,每个元素 x
出现的次数可以是 0, 1, ..., occ[x]
次,其中 occ[x]
是元素 x
在数组中的出现次数。
注意:
- 如果两个子多重集合中的元素排序后一模一样,那么它们两个是相同的 子多重集合 。
- 空 集合的和是
0
。
数据范围:
$1\le nums.len \le 2 \times 10^4; 0\le l \le r \le 2\times 10^4$
题解:
多重背包求方案数,枚举每种元素取得个数。
该问题可以设 $dp[i][j]$ 为不超过容量 $j$ 的方案数,然后 $dp[n][r] - dp[n][l - 1]$ 。也可以求恰好的,然后 $\sum_{v = l}^rdp[n][v]$ 。二者的区别就在于初始化,不超过需要初始化 $dp[0][0,\cdots,r] = 1$ ,恰好需要初始化 $dp[0][0] = 1$ 。
假设 $dp[i][j]$ 表示前 $i$ 种元素,恰好装满背包 $j$ 有多少种方案。则对于第 $i$ 种,枚举选的个数。
但是复杂度比较高,复杂度大约为 $O(r \times n) \approx 4\times10^8$ ,会超时。
方法一,展开公式求解:
展开递推式:
因为每一个都要枚举 $k + 1$ 次,所以项数是一样的。
一般的有:
需要多减去一项。
如果 $j - (cnt[v] + 1)v < 0$ 则:
实际上就是一个窗口大小为 $[j - kv, j]$ 的滑动窗口,其实就是长度为 $cnt[v] + 1$ 。而且每种余数 $j\%v$ 都有一个滑动窗口,求滑动窗口内部的累加值,滑动时,每次加一个尾部的数字,减去头部数字。
需要注意的是, $dp[i][j]$ 在 $[0, v)$ 还有一段。
另外一个需要注意的地方就是 $0$ 需要单独计算贡献。因为不同数量的 $0$ 在dp数组中都是同一个格子,会被重复计算。首先计算出来不包含 $0$ 的方案数,然后用 $cnt[0] + 1$ 乘以该答案。
方法二,前缀和优化:
代码:
1 | auto optimize_cpp_stdio = []() |