题目大意:
已知X[n]数列,定义f(L,R)为 X[i] 下标区间(L,R) 中部分数(即子集)求和为S的数量,求
N<=1e3,S<=1e3
解题思路:
我们假如已经知道区间(l,r)最左和最右的端点是满足题意的,那么根据乘法原理,这一小段区间对最后的答案所作的贡献就是:l*(n-r+1). 那么我们可以开始枚举r点,然后令dp[l][n]代表前l项中,和为n的最左的端点的下标的和。所以,我们得到答案为:
,其中
dp的转移为:
当
首先dp[i-1][n]很好解释,就是本次第i项对dp不作贡献。dp[i-1][n-a[i]]代表就是第i项为求和做贡献了但是注意这里我们不需要加上i,因为第i项不会是最左的下标。
当
这里就是第i项直接作贡献的情况。
这里有一些学习的点。首先我们这里不能用简单的dp的思维直接套状态比如:dp[l][r]得出这段区间的求和数。需要用到贡献法的思维,这里是从一小段区间往外推求得的贡献。另外这里,为什么可以枚举r端点呢,主要是因为左边的下标的和是可以转移的!
#include <bits/stdc++.h>
#define int long long
const int MODN=998244353;
const int MAXN=3010;
using namespace std;
int dp[2][MAXN];
//滚动数组
int32_t main(){
dp[1][0]=1;
int cur=1;
int n,k;cin>>n>>k;
vector<int> arrmv(n+1);
for(int i=0;i<n;i++)cin>>arrmv[i+1];
vector<int> a=arrmv;
int ans=0;
for(int i=1;i<=n;i++){
if(k-a[i]>=0){
if(k-a[i]>0)ans+=dp[cur][k-a[i]]*(n-i+1);
else ans+=i*(n-i+1);
ans%=MODN;
}
cur=!cur;
for(int j=k;j>=0;j--){
dp[cur][j]=0;
dp[cur][j]+=dp[!cur][j];
if(j-a[i]>0)dp[cur][j]+=dp[!cur][j-a[i]];
else if(j==a[i])dp[cur][j]+=i;
dp[cur][j]%=MODN;
}
}
cout<<ans<<endl;
return 0;
}
//无滚动
// #include <bits/stdc++.h>
// #define int long long
// const int MODN=998244353;
// const int MAXN=3010;
// using namespace std;
// int dp[MAXN][MAXN];
// int32_t main(){
// dp[0][0]=1;
// int n,k;cin>>n>>k;
// vector<int> arrmv(n+1);
// for(int i=0;i<n;i++)cin>>arrmv[i+1];
// vector<int> a=arrmv;
// int ans=0;
// for(int i=1;i<=n;i++){
// if(k-a[i]>=0){
// if(k-a[i]>0)ans+=dp[i-1][k-a[i]]*(n-i+1);
// else ans+=i*(n-i+1);
// ans%=MODN;
// }
// for(int j=k;j>=0;j--){
// dp[i][j]+=dp[i-1][j];
// if(j-a[i]>0)dp[i][j]+=dp[i-1][j-a[i]];
// else if(j==a[i])dp[i][j]+=i;
// dp[i][j]%=MODN;
// cout<<dp[i][j]<<" ";
// }
// cout<<endl;
// }
// cout<<ans<<endl;
// return 0;
// }