跳到主要内容

BISHI131. 数楼梯

题目描述​

给定一段有 n 阶的楼梯,你每一步可以选择上 1 阶或者 2 阶。 求从楼梯底端走到顶端共有多少种不同的走法。 由于答案可能很大,请将结果对 998244353 取模后输出。

输入格式​

在一行上输入一个整数 n,表示楼梯的阶数。

输出格式​

输出一个整数,表示不同走法的数量对 998244353 取模后的结果。

示例​

示例 1​

输入:

1

输出:

1

说明: 1 阶楼梯只有「迈 1 阶」一种走法。

示例 2​

输入:

4

输出:

5

说明: 5 种走法分别是:1+1+1+1、1+1+2、1+2+1、2+1+1、2+2。

示例 3​

输入:

721

输出:

670483856

说明: n 很大时答案会溢出,必须在计算过程中对 998244353 取模。


解题思路​

第一步:理解问题本质​

设 f(n) 表示「走到第 n 阶的走法数」。考虑最后一步:

  • 如果最后一步迈 1 阶,那么之前站在第 n-1 阶,有 f(n-1) 种走法;
  • 如果最后一步迈 2 阶,那么之前站在第 n-2 阶,有 f(n-2) 种走法。

最后一步只有这两种可能,所以:

f(n) = f(n-1) + f(n-2)

边界:f(1) = 1(迈 1 阶),f(2) = 2(1+1 或 2)。

第二步:暴力递归​

直接按公式递归:

def f(n):
if n <= 2:
return n
return f(n - 1) + f(n - 2)

它会重复计算大量相同的子问题(f(n-2) 被算了很多遍), 复杂度是 O(2^n),n=721 时完全跑不动。

第三步:递推 + 滚动变量(最优)​

把递归改成自底向上的递推,用两个变量滚动保存最近两项:

a, b = 1, 1
for _ in range(n):
a, b = b, (a + b) % MOD
# 循环结束后 a 就是答案

因为取模对加法满足结合律与交换律,每步都取模不改变最终结果, 又能避免大整数越滚越慢。


完整代码实现​

本题无可视化步骤:代码里没有可逐帧展示的过程(执行轨迹已经录下来, 但局部变量里找不到推进中的中间状态 —— 比如全篇就是一次正则替换或一次切片)。

import sys

MOD = 998244353


def solve() -> None:
"""斐波那契递推,滚动变量 + 取模"""
data = sys.stdin.read().split()
if not data:
return
n = int(data[0])
a, b = 1, 1 # a = f(1), b = f(2);滚动到 a = f(n+1)
for _ in range(n):
a, b = b, (a + b) % MOD
sys.stdout.write(str(a % MOD) + "\n")


if __name__ == "__main__":
solve()

示例推演​

以 n = 4 为例,初始 a = 1, b = 1:

轮次计算前 (a, b)a + b计算后 (a, b)
1(1, 1)2(1, 2)
2(1, 2)3(2, 3)
3(2, 3)5(3, 5)
4(3, 5)8(5, 8)

循环结束后 a = 5,即答案为 5,与示例一致。


复杂度分析​

解法时间复杂度空间复杂度说明
朴素递归O(2^n)O(n)大量重复计算
递推数组O(n)O(n)保存整个 dp 数组
滚动变量O(n)O(1)推荐

易错点总结​

1. 忘记取模​

n 到 721 时斐波那契数有约 150 位,虽然 Python 大整数不会溢出, 但数值越滚越大、运算越来越慢;更别说其它语言会直接溢出。 按题目要求在每一步取模。

2. 边界与下标对不上​

f(1) = 1、f(2) = 2,不是 f(1) = 1、f(2) = 1。 如果写成标准斐波那契(1,1,2,3,5…),需要确认第 n 项对应的是哪个值。

3. 每步都取模 vs 最后取模​

两者结果相同(题目备注也强调了这一点),但每步取模更快、更安全。


扩展思考​

  • 若允许一次上 1、2、3 阶,递推变成 f(n) = f(n-1)+f(n-2)+f(n-3)。
  • 若相邻两步不能相同(比如不能连续迈两次 2 阶),需要给状态加上 「最后一步是几」,变成带状态维度的 DP。

相关题目​

自测​

加载评论中...