题目: 传送门
思路:
显而易见的想法就是暴力dp,dp[i][j]表示从前i个年货中取出j个的方案个数,那么应该有 d p [ i ] [ j ] = ∑ k = 0 a [ i ] d p [ i − 1 ] [ j − k ] , j − k > = 0 dp[i][j] = \sum_{k=0}^{a[i]}dp[i-1][j-k], j-k>=0 dp[i][j]=∑k=0a[i]dp[i−1][j−k],j−k>=0
- 由于数据量大,不能直接写二维dp,注意到状态转移方程求i时,只用到了i-1,故可以使用滚动数组(注意更新dp[j]的先后顺序,不能用被覆盖掉的上一轮的值来更新)
- 又因为涉及到求和,直接写有 O ( n ∗ a [ i ] ∗ k ) O(n*a[i]*k) O(n∗a[i]∗k)将近 1 0 13 10^{13} 1013的复杂度,我们对dp数组求前缀和,就可以在 O ( 1 ) O(1) O(1)的时间内求和,从而将复杂度降到了 O ( n ∗ a [ i ] ∗ 1 ) O(n*a[i]*1) O(n∗a[i]∗1) 最大只有 1 0 8 10^8 108,就可以AC了
Code:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <vector>
#include <queue>
#include <map>
#include <algorithm>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int maxn = 100007;
const ll mod = 998244353;
ll dp[maxn];//(优化成一维滚动数组数组之前)dp[i][j]表示从前i个年货里取了j个的方案数
ll sum[maxn];//sum是dp数组的前缀和
int arr[maxn];
int main()
{
int n, k;
scanf("%d %d", &n, &k);
for (int i=1;i<=n;i++) {
scanf("%d", &arr[i]);
}
dp[0] = 1;
sum[0] = 1;
for (int i=1;i<=n;i++) {
//先求前缀和
for (int j=1;j<=k;j++) {
sum[j] = (sum[j-1] + dp[j]) % mod;
}
//更新dp
for (int j=k;j>=0;j--) {
//求dp[j]+dp[j-1]+...+dp[j-arr[i]] 还要保证下标>=0
dp[j] = (sum[j] - sum[j - min(j, arr[i]) - 1] + mod) % mod;
}
}
ll ans = 0;
for (int i=0;i<=k;i++) {
ans = (ans + dp[i]) % mod;
}
printf("%lld\n", ans);
return 0;
}