题目描述
难度分:865
输入n(1≤n≤3000)和两个长为n的数组a和b,元素范围在[0,3000],且均为递增数组(允许有相同元素)。
构造递增数组c(允许有相同元素),满足a[i]≤c[i]≤b[i]。
输出你能构造多少个不同的c,由于答案可能较大,对其模998244353后输出。
输入样例1
2
1 1
2 3
输出样例1
5
输入样例2
3
2 2 2
2 2 2
输出样例2
1
输入样例3
10
1 2 3 4 5 6 7 8 9 10
1 4 9 16 25 36 49 64 81 100
输出样例3
978222082
算法
动态规划
这种计数类题目一般要么是纯数学题,要么就是DP
,或者两者的结合。
状态定义
dp[i][cur]表示第i个数为cur时,能够得到的方案数。显然,答案就是Σ3000val=0dp[n][val]。
状态转移
第i个数cur的范围应该是[max(a[i],last),b[i]],状态转移方程为
dp[i][cur]=Σ3000last=0dp[i−1][last],cur∈[max(a[i],last),b[i]]
其中last是上一个数c[i−1],如果双重循环枚举last和cur,时间复杂度会达到O(n3),在n≤3000的数据规模下会超时。因此,可以先计算一下dp[i−1]的前缀和数组s,然后进行O(1)的状态转移。
dp[i][cur]=Σcurx=0dp[i−1][x]=s[cur],cur∈[a[i],b[i]]
复杂度分析
时间复杂度
外层循环i时间复杂度为O(n),内层分别循环last(计算前缀和)和cur(状态转移)时间复杂度为O(n),算法整体的时间复杂度为O(n2)。
空间复杂度
空间瓶颈在于DP
数组,状态数量为O(n2)级别,因此额外空间复杂度为O(n2)(也可以滚动数组优化到O(n))。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 3010, MOD = 998244353;
int n, a[N], b[N], s[N], dp[N][N];
int windowSum(int l, int r) {
if(l > r) return 0;
return (s[r] - (l? s[l - 1]: 0) + MOD) % MOD;
}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
for(int i = 1; i <= n; i++) {
scanf("%d", &b[i]);
}
memset(dp, 0, sizeof dp);
for(int i = a[1]; i <= b[1]; i++) {
dp[1][i] = 1;
}
for(int i = 2; i <= n; i++) {
for(int last = 0; last <= 3000; last++) {
s[last] = ((last? s[last - 1]: 0) + dp[i - 1][last]) % MOD;
}
for(int cur = a[i]; cur <= b[i]; cur++) {
dp[i][cur] = (dp[i][cur] + windowSum(0, cur)) % MOD;
}
}
int ans = 0;
for(int i = 0; i <= 3000; i++) {
ans = (ans + dp[n][i]) % MOD;
}
printf("%d\n", ans);
return 0;
}