终于有时间来慢慢补补题了
作为队内的dp手,赛时想了好久,等学弟学妹都出了还是不会,羞愧,还好最终队友做出来了。
链接J Qu’est-ce Que C’est?
长度为 n n n 的数组 a a a,每个数的取值范围 a i = [ − m , m ] a_i = [-m, m] ai=[−m,m],问所有满足长度 > 1 >1 >1 的子段的和为非负数的数组可能的数量。 1 ≤ n , m ≤ 5000 1\leq n,m\leq 5000 1≤n,m≤5000。
看了懵哥的题解,看到状态定义后就会了,我怎么就想不到呢?
法一
dp状态定义 f [ i ] [ j ] : f[i][j]: f[i][j]: 前 i i i 个数,最小后缀和为 j j j 的方案数 j = [ − 5000 , 5000 ] j = [-5000, 5000] j=[−5000,5000]。因为是最小后缀和,那么如果有后缀 < − 5000 <-5000 <−5000 的说明至少是两个数以上的和为负数,与题意不符是不合法的方案,所以数组大小开到 [ 0 , 10000 ] [0,10000] [0,10000] 即可(离散化后)。
我们先不管时间复杂度,根据dp定义先敲个状态转移出来,代码如下:
#include
using namespace std;
#define ll long long
const int N = 5010, mod = 998244353;
int f[N][N * 2]; // 前i个数,最小后缀和为j的方案数 j = [-5000, 5000]
void add(int& a, int b){
a = (a + b) % mod;
}
int main(){
int n, m;
cin >> n >> m;
f[0][2 * m] = 1;
for(int i = 1; i <= n; i ++){
for(int j = -m; j <= m; j ++){ // 枚举最小后缀
for(int k = m; k >= -m; k --){ // 枚举当前a_i选哪个数
if(j + k < 0) break;
add(f[i][min(j + k, k) + m], f[i - 1][j + m]);
}
}
}
int ans = 0;
for(int i = -m; i <= m; i ++){
add(ans, f[n][i + m]);
}
cout << ans;
return 0;
}
时间复杂度为 O ( n 3 ) O(n^3) O(n3),我们观察一下代码如何优化,发现会有大量连续的 k k k, f [ i ] [ min ( j + k , k ) ] f[i][\min(j + k, k)] f[i][min(j+k,k)] 加的是同一个状态 f [ i − 1 ] [ j ] f[i - 1][j] f[i−1][j]。那么经典优化方案不就来了吗,差分前缀和优化。
以下分情况讨论:
因为是 min ( j + k , k ) \min(j + k, k) min(j+k,k)
// 区间 [l, r] + val f_l + val f_{r + 1} + val
for(int j = -m; j <= 0; j ++){
f[i][m] = (f[i][m] + f[i - 1][j + m]) % mod;// 差分 +
f[i][m + j + m + 1] = (f[i][m + j + m + 1] + mod - f[i - 1][j + m]) % mod;// 差分 -
}
for(int j = 1; j <= m; j ++){
f[i][-j + m] = (f[i][-j + m] + f[i - 1][j + m]) % mod;
f[i][m + m + 1] = (f[i][m + m + 1] + mod - f[i - 1][j + m]) % mod;
}
#include
using namespace std;
#define ll long long
const int N = 5010, mod = 998244353;
int f[N][N * 2]; // 前i个数,最小后缀和为j的方案数 j = [-5000, 5000]
void add(int& a, int b){
a = (a + b) % mod;
}
int main(){
int n, m;
cin >> n >> m;
f[0][2 * m] = 1;
for(int i = 1; i <= n; i ++){
/*
for(int j = -m; j <= m; j ++){
for(int k = m; k >= -m; k --){
if(j + k < 0) break;
add(f[i][min(j + k, k) + m], f[i - 1][j + m]);
}
}
*/
// 考虑差分前缀和优化
// j 是 负数 k 是正数 f[i][j + k + m] 从0 ~ m + j
// j 是 正数 k 是负数/正数 f[i][k + m] 从-j ~ m
for(int j = -m; j <= 0; j ++){
f[i][m] = (f[i][m] + f[i - 1][j + m]) % mod; // 差分 +
f[i][m + j + m + 1] = (f[i][m + j + m + 1] + mod - f[i - 1][j + m]) % mod; // 差分 -
}
for(int j = 1; j <= m; j ++){
f[i][-j + m] = (f[i][-j + m] + f[i - 1][j + m]) % mod;
f[i][m + m + 1] = (f[i][m + m + 1] + mod - f[i - 1][j + m]) % mod;
}
for(int j = -m + 1; j <= m; j ++){ // 前缀和
f[i][j + m] = (f[i][j + m] + f[i][j + m - 1]) % mod;
}
}
int ans = 0;
for(int i = -m; i <= m; i ++){
add(ans, f[n][i + m]);
}
cout << ans;
return 0;
}