题意:
给定长度为n的数列,要求每个数都在
的范围,且任意长度大于等于2的区间和都大于等于0,问方案数。
。
思路:
首先要看出是dp题,用来表示遍历到第i位且后缀和最小为x的可行方案数(此时的后缀可以只有最后一位)。很显然j的值在区间
。下面考虑dp如何转换:
1.对于。 先讨论
,
可由
加一位值为
转换而来;也可由
加一位值为0 转换而来。就有
。再讨论
,可由
,加一位值为
转换而来;也可由
加一位值为1转换而来。例如m=5时,dp[2][3]可以由dp[1][-2]然后末尾添加5转换来。就有
。依次讨论可以得出
可以由
,末位加值为
转换而来;也可由
,末位加x转换而来。综上所诉:
2.对于。可以去验证,只有
,末位加值为x才能转换成
。例如m=5时,dp[2][-3]可以由dp[1][3]然后末尾添加-3转换来。所以
。
为了方便计算我们把这个区间平移映射到
区间上。按照上述思想去找新的dp转换式就有:
由于都是求和到2m,所以可以考虑后缀和优化。
代码:
//#define _CRT_SECURE_NO_WARNINGS
//#include<iostream>
//#include<algorithm>
//#include<cstdio>
//#include<map>
//#include<string.h>
//#include<string>
//#include<vector>
//#include<__msvc_all_public_headers.hpp>
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod = 998244353;
const int N = 5005;
ll dp[N][N*2];//dp[i][j]表示遍历到i位,后缀和最小为j且合法的数量。(这里后缀和包含了只含有最后一位的情况)
ll sum[N * 2];//后缀数组
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, m;
cin >> n >> m;
ll ans = 0;
//初始化
for (int i = 0; i <= m*2; i++)
{
dp[1][i] = 1;
}
for (int i = 2; i <= n; i++)
{
//处理后缀和
for (int j = m * 2; j >= 0; j--)sum[j] = (sum[j + 1] + dp[i - 1][j]) % mod;
//[0,m)的情况
for (int j = 0; j < m; j++)
{
dp[i][j] = sum[2 * m - j];
}
//[m,2m]的情况
for (int j = m; j <= 2 * m; j++)
{
dp[i][j] = sum[j - m];
}
}
//统计
for (int i = 0; i <= m * 2; i++)
{
ans = (ans + dp[n][i]) % mod;
}
cout << ans << endl;
return 0;
}