给定数组nums[n]和整数l,r,nums中的元素可能会重复,要求从nums中选择若干个元素,其元素和在[l,r]内,有多少种不同方案,结果对1E9+7取模。注:空集合的结果为0,相等的元素之间没有区别。
1<=n<=2E4; 0<=nums[i]<=2E4; sum(nums[i])<=2E4; 0<=l<=r<=2E4
分析:
1、存在相等元素,且没有区别,可以看成是一个多重背包问题。
2、由于sum(nums[i])<=2E4,可以估算出不同元素的个数(记为m)不超过200,因为1+2+..+200>2E4。
3、如果用二进制优化,时间复杂度会超,因此要用类似单调队列优化dp的方式优化掉时间复杂度中的log,大致思路如下:
(1)状态转移方程:dp[i][j] = dp[i-1][j] + dp[i-1][j-v] + dp[i-1][j-2v] + dp[i-1][j-3v] + ...,可以看到第二维模v同余,即计算dp[i][j]时,只依赖dp[i-1]只与j同余的那些项。
(2)外层按余数0~v-1从小到大的顺序,内层按a, a+v, a+2v的顺序,计算dp。对于某个固定的余数,由于j较大时,未必有足够的相同元素,因此用滑动窗口来维护。
4、元素0需要单独处理。
int dq[20005];
class Solution {
public:
int countSubMultisets(vector<int>& nums, int l, int r) {
int n = nums.size();
std::sort(nums.begin(), nums.end());
std::vector<std::pair<int,int>> vp;
int cnt0 = 0;
for (int i = 0, j = 0; i < n; i = j) {
for (j = i; j < n && nums[j] == nums[i]; j++);
if (nums[i] == 0) {
cnt0 = j - i;
} else {
vp.emplace_back(nums[i], j - i);
}
}
int m = vp.size();
std::vector<mint> dp1(r + 1), dp2(r + 1);
for (int i = 0; i <= m; i++) {
dp1[0] = 1;
}
for (int i = 1; i <= m; i++) {
int val = vp[i-1].first;
int cnt = vp[i-1].second;
for (int j = 0; j < val; j++) {
mint sum = 0;
int L = 0, R = 0;
for (int k = j; k <= r; k += val) {
sum += dp1[k];
dq[R++] = k;
if (dq[R-1] - dq[L] > val * cnt) {
sum -= dp1[dq[L++]];
}
dp2[k] = sum;
}
}
std::swap(dp1, dp2);
}
mint ans = 0;
for (int j = l; j <= r; j++) {
ans += dp1[j];
}
if (cnt0) ans *= cnt0 + 1;
return ans.val();
}
};
标签:leetcode2902,多重,dp2,nums,int,元素,std,集合,dp
From: https://www.cnblogs.com/chenfy27/p/18667205