题解:学而思编程 变种走格子问题
【题目来源】
学而思编程:变种走格子问题
【题目描述】
\(n\) 个格子排成 \(1\) 列,从左至右编号为 \(1\sim n\)。他一开始站在第 \(1\) 个格子上,每次只能向右走若干格。但是小猴一次可以走的格子数 \(x\) 必须满足以下要求:
存在 \(1\le i\le k\),使得 \(x\) 满足 \(L_i\le x\le R_i\)。
其中 \(k\) 是一个不超过 \(10\) 的正整数,\([L_1,R_1],[L_2,R_2],\dots,[L_k,R_k]\) 由输入给出。
例如 \(L_1=1,R_1=1,L_2=3,R_2=4\),那么小猴一次走的格子数只能是 \(1,3,4\) 中的某个数。
问:小猴要走到第 \(n\) 格,一共有多少种方法?(输出答案除以 \(998244353\) 的余数)
【输入】
第 \(1\) 行,\(2\) 个正整数 \(n\),\(k\)
接下来 \(k\) 行,每行 \(2\)个正整数 \(L_i,R_i\)
【输出】
输出方法数除以 \(998244353\) 的余数
【输入样例】
5 2
1 1
3 4
【输出样例】
4
【核心思想】
-
问题分析:给定 \(n\) 个格子排成一列,从第 \(1\) 格出发,每次可向右跳跃 \(x\) 格,其中 \(x\) 属于 \(k\) 个给定区间 \([L_i, R_i]\) 的并集。求到达第 \(n\) 格的方法数模 \(998244353\)。这是一个线性动态规划 + 前缀和优化问题,关键在于将多区间跳跃转化为区间求和,并利用前缀和实现 \(O(1)\) 区间查询。
-
算法选择:
- 线性 DP:
dp[i]表示到达第 \(i\) 格的方法数 - 前缀和优化:利用前缀和数组
s[i]将"从多个前驱位置转移"的区间求和优化到 \(O(1)\) - 区间容斥:对每个跳跃区间 \([L_i, R_i]\),用前缀和的差分计算合法前驱区间的方案数之和
- 线性 DP:
-
关键步骤:
- 初始化:
- 读取 \(n\)(格子数)和 \(k\)(跳跃方式数)
- 读取 \(k\) 个区间 \([L_i, R_i]\)
dp[1] = 1,s[1] = 1:从第 \(1\) 格出发有 \(1\) 种方法
- DP 转移(遍历 \(i\) 从 \(2\) 到 \(n\)):
- 对每个跳跃区间 \([L_j, R_j]\)(\(1 \leq j \leq k\)):
- 能跳到 \(i\) 的前驱位置范围为 \([i - R_j, i - L_j]\)
dp[i] += s[i - L_j]:加上从位置 \(i - L_j\) 及之前的所有方案数(前缀和)dp[i] -= s[i - R_j - 1]:减去从位置 \(i - R_j - 1\) 及之前的方案数,实现区间 \([i - R_j, i - L_j]\) 的精确求和- 每次操作后取模
s[i] = (s[i - 1] + dp[i]) \bmod MOD:更新前缀和
- 对每个跳跃区间 \([L_j, R_j]\)(\(1 \leq j \leq k\)):
- 输出:
(dp[n] + MOD) \bmod MOD:处理负数后输出
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(n \times k)\),每个位置遍历 \(k\) 个跳跃区间,每个区间 \(O(1)\) 转移
- 空间复杂度:\(O(n)\),DP 数组
dp[1..n]和前缀和数组s[1..n]
-
前缀和区间求和的核心思想:
- 转移方程转化:
dp[i] = \sum_{j=1}^{k} \sum_{x=L_j}^{R_j} dp[i - x],直接计算为 \(O(n \times k \times \max(R_j - L_j))\) - 区间压缩:对于固定区间 \([L_j, R_j]\),内层求和 \(\sum_{x=L_j}^{R_j} dp[i - x] = \sum_{t=i-R_j}^{i-L_j} dp[t] = s[i - L_j] - s[i - R_j - 1]\),降为 \(O(1)\)
- 前缀和定义:
s[i] = \sum_{t=1}^{i} dp[t],维护到达每个位置及之前所有位置的方法数前缀和 - 边界处理:
i - R_j - 1 \geq 0时才有意义,否则不减;i - L_j \geq 0保证前驱位置存在 - 模运算修正:减法后可能为负,输出时加
MOD再取模确保结果非负 - 适用于"多区间跳跃 + 到达计数"类问题,前缀和可将 \(O(n \times k \times V)\) 优化至 \(O(n \times k)\)
- 转移方程转化:
【解题思路】

【算法标签】
线性DP-一维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int MR = 2e5 + 10;
const int MOD = 998244353;
typedef long long LL;
int n, k;
int L[15], R[15]; // L[i], R[i] 表示第i种跳跃的长度范围
int s[MR], dp[MR]; // s是前缀和数组,dp是动态规划数组
int main()
{
cin >> n >> k; // 输入总台阶数n和跳跃方式数k
for (int i = 1; i <= k; i++)
cin >> L[i] >> R[i]; // 输入每种跳跃的长度范围
// 初始化
dp[1] = s[1] = 1; // 从第1个台阶出发,只有1种方法
// 动态规划
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= k; j++) {
// 从[i-R[j], i-L[j]]范围内的台阶可以跳到i
if (i - L[j] >= 0) {
dp[i] = (dp[i] + s[i - L[j]]) % MOD;
}
if (i - R[j] - 1 >= 0) {
dp[i] = (dp[i] - s[i - R[j] - 1]) % MOD;
}
}
// 更新前缀和
s[i] = (s[i - 1] + dp[i]) % MOD;
}
// 输出结果,处理负数
cout << (dp[n] + MOD) % MOD << endl;
return 0;
}
【运行结果】
5 2
1 1
3 4
4
浙公网安备 33010602011771号