题目描述
难度分:1876
输入n(1≤n≤3000)、s(1≤s≤3000)和长为n的数组a(1≤a[i]≤3000)。
- 定义f(L,R)等于:在子数组a[L],a[L+1],…,a[R]中,元素和恰好等于s的子序列的个数。
输出所有f(L,R)的和,其中0≤L≤R≤n。
模998244353。
输入样例1
3 4
2 2 4
输出样例1
5
输入样例2
5 8
9 9 9 9 9
输出样例2
0
输入样例3
10 10
3 1 4 1 5 9 2 6 5 3
输出样例3
152
算法
01
背包
状态定义
定义f[i][j]表示子数组右端点为i(左端点任意),子序列和为j的方案数。显然,本题的答案就是f[0][s]+f[1][s]+…+f[n−1][s]。
状态转移
每个元素a[i]都可以选或者不选,因此状态转移方程为
f[i][j]=f[i−1][j]+f[i−1][j−a[i]]
这里需要注意一下s=0时的初始值f[i][0]=i。如f[2][0]表示子数组[2,2]中有1个子序列(空序列)和为0,子数组 [1,2]中有1个子序列(空序列)和为0,所以f[2][0]=2。
复杂度分析
时间复杂度
整个算法是一个01
背包的流程,由于S和N是同一个规模的,因此时间复杂度为O(n2)。
空间复杂度
利用内层循环倒序遍历优化掉了第一维,额外空间复杂度就是f数组的大小,为O(n)。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 3010, MOD = 998244353;
int f[N], a[N], n, s;
int main() {
scanf("%d%d", &n, &s);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
int ans = 0;
for(int i = 1; i <= n; i++) {
f[0]++;
for(int j = s; j >= a[i]; j--) {
f[j] = (f[j] + f[j - a[i]]) % MOD;
}
ans = (ans + f[s]) % MOD;
}
printf("%d\n", ans);
return 0;
}