题解:学而思编程 在两个数列之间
【题目来源】
学而思编程:在两个数列之间
【题目描述】
有两个整数数列 \(a_1,a_2,\dots,a_n\) 和 \(b_1,b_2,\dots,b_n\)。我们的任务是找出满足以下条件的数列 \(c_1,c_2,\dots,c_n\)。
(1)对 \(i=1,2,\dots,n,a_i\le c_i\le b_i\)
(2)对 \(i=1,2,\dots,n-1,c_i\le c_{i+1}\)
(3)所有 \(c_i\) 都是整数
满足这些条件的整数数列有多少个?输出答案模 \(998244353\) 的余数。
【输入】
第 \(1\) 行,\(1\) 个正整数 \(n\)。
第 \(2\) 行,\(n\) 个整数 \(a_1,a_2,\dots,a_n\)。
第 \(3\) 行,\(n\) 个整数 \(b_1,b_2,\dots,b_n\)。
【输出】
\(1\) 个整数,输出答案模 \(998244353\) 的余数。
【输入样例】
2
1 1
2 3
【输出样例】
5
【核心思想】
-
问题分析:给定两个整数数列 \(a_i\) 和 \(b_i\),要求构造数列 \(c_i\) 满足 \(a_i \leq c_i \leq b_i\) 且 \(c_i \leq c_{i+1}\)。求满足条件的整数数列个数模 \(998244353\)。这是一个线性动态规划 + 前缀和优化问题,关键在于利用非递减约束将状态转移转化为前缀和查询。
-
算法选择:
- 线性 DP:以位置 \(i\) 和当前取值 \(j\) 作为状态维度,
f[i][j]表示第 \(i\) 个位置取 \(j\) 时的方案数 - 前缀和优化:利用前缀和数组
s[i][j]将"所有 \(c_{i-1} \leq j\) 的方案数之和"从 \(O(\text{值域})\) 降为 \(O(1)\)
- 线性 DP:以位置 \(i\) 和当前取值 \(j\) 作为状态维度,
-
关键步骤:
- 初始化(第 \(1\) 个位置):
- 遍历 \(j\) 从 \(a_1\) 到 \(b_1\):
f[1][j] = 1:第 \(1\) 个位置取 \(j\) 只有 \(1\) 种方案s[1][j] = (s[1][j-1] + f[1][j]) \bmod mod:前缀和数组,\(s[1][j] = \sum_{k=a_1}^{j} f[1][k]\)
- 遍历 \(j\) 从 \(a_1\) 到 \(b_1\):
- DP 转移(第 \(2\) 个位置到第 \(n\) 个位置):
- 遍历 \(i\) 从 \(2\) 到 \(n\):
- 遍历 \(j\) 从 \(a_i\) 到 \(b_i\):
- 非递减约束要求 \(c_{i-1} \leq c_i = j\),即前一位取值范围为 \([a_{i-1}, \min(b_{i-1}, j)]\)
f[i][j] = s[i-1][\min(b_{i-1}, j)] - s[i-1][a_{i-1} - 1]:利用前缀和 \(O(1)\) 查询合法方案数- 若
f[i][j] < 0,加mod修正负数 s[i][j] = (s[i][j-1] + f[i][j]) \bmod mod:维护当前层前缀和
- 遍历 \(j\) 从 \(a_i\) 到 \(b_i\):
- 遍历 \(i\) 从 \(2\) 到 \(n\):
- 统计答案:
sum = \sum_{j=a_n}^{b_n} f[n][j] \bmod mod:第 \(n\) 个位置所有合法取值的方案数之和
- 输出
sum
- 初始化(第 \(1\) 个位置):
-
时间/空间复杂度:
- 时间复杂度:\(O(n \times V)\),\(V\) 为值域大小(最大 \(b_i\)),每个状态 \(O(1)\) 转移
- 空间复杂度:\(O(n \times V)\),DP 数组
f[n][V]和前缀和数组s[n][V]
-
前缀和优化的核心思想:
- 状态设计:
f[i][j]表示第 \(i\) 个位置取值为 \(j\) 时的方案数,状态维度为位置和取值 - 约束转化:非递减条件 \(c_{i-1} \leq c_i\) 转化为"前一位取值不超过当前值",即 \(\sum_{k=a_{i-1}}^{\min(b_{i-1}, j)} f[i-1][k]\)
- 前缀和降维:前缀和数组
s[i][j] = \sum_{k=a_i}^{j} f[i][k],将区间求和从 \(O(\text{区间长度})\) 优化到 \(O(1)\) - 值域压缩处理:通过
\min(b_{i-1}, j)处理前一位上界与当前值的大小关系,确保查询范围合法 - 模运算处理:减法后及时加模数修正负数,保证结果非负
- 适用于"序列构造 + 相邻约束 + 计数"类问题,前缀和可将 \(O(n \times V^2)\) 优化至 \(O(n \times V)\)
- 状态设计:
【解题思路】

【算法标签】
线性DP-一维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int mod = 998244353;
int a[3005], b[3005], n;
int f[3005][3005], s[3005][3005];
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]); // 读取每个位置的上界
// 初始化第一个位置
for (int j = a[1]; j <= b[1]; j++) {
f[1][j] = 1; // 第一个位置选j只有1种方案
s[1][j] = (s[1][j-1] + f[1][j]) % mod; // 前缀和
}
// DP计算
for (int i = 2; i <= n; i++) {
for (int j = a[i]; j <= b[i]; j++) {
// 计算f[i][j]:第i个位置选j的方案数
if (j >= a[i-1]) { // 满足非递减条件
f[i][j] = (f[i][j] + s[i-1][min(b[i-1], j)] - s[i-1][a[i-1]-1]) % mod;
// 减去可能的负数
if (f[i][j] < 0) f[i][j] += mod;
}
// 计算前缀和
s[i][j] = (s[i][j-1] + f[i][j]) % mod;
}
}
// 计算结果
int sum = 0;
for (int j = a[n]; j <= b[n]; j++)
sum = (sum + f[n][j]) % mod;
printf("%d\n", sum);
return 0;
}
【运行结果】
2
1 1
2 3
5
浙公网安备 33010602011771号