P10250 [GESP样题 六级] 下楼梯

记录80

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
long long dp[65];
int main(){
	int n;
	cin>>n;
	dp[1]=1,dp[2]=2,dp[3]=4;
	for(int i=4;i<=n;i++) dp[i]=dp[i-1]+dp[i-2]+dp[i-3];
	cout<<dp[n];
	return 0;
} 

题目传送门https://www.luogu.com.cn/problem/P10250


突破口

顽皮的小明发现,下楼梯时每步可以走 1 个台阶、2 个台阶或 3 个台阶。现在一共有 N 个台阶,你能帮小明算算有多少种方案吗?


思路

🔍 一、题目思路分析

1. 问题本质:爬楼梯变种(三步可达)
  • 小明在第 0 级台阶,要走到第 N 级
  • 每次可以走 1 步、2 步 或 3 步
  • 问:有多少种不同的走法?

这是经典的 动态规划(DP) 问题,是"斐波那契数列"的扩展。


2. 状态定义

设 dp[i] 表示走到第 i 级台阶的方案数。

3. 状态转移方程

要到达第 i 级,最后一步可能是:

  • 从 i−1 走 1 步
  • 从 i−2 走 2 步
  • 从 i−3 走 3 步

所以:

dpi=dpi−1+dpi−2+dpi−3dpi=dpi−1+dpi−2+dpi−3

4. 初始条件(边界)
  • dp[0] = 1(站在地面,1 种方案:不动)
    但本题从 N≥1 开始,且样例给出:
    • N=1 → 1 种(1)
    • N=2 → 2 种(1+1, 2)
    • N=3 → 4 种(1+1+1, 1+2, 2+1, 3)

所以直接初始化:

  • dp[1] = 1
  • dp[2] = 2
  • dp[3] = 4

✅ 验证 N=4:

  • dp[4] = dp[3] + dp[2] + dp[1] = 4 + 2 + 1 = 7 ✅(符合样例)

代码分析

✅ 代码逐行解释

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
long long dp[65];
  • 引入标准库
  • 定义 dp 数组,大小 65(因为 N ≤ 60,留余量)
  • 使用 long long:防止结果溢出(N=60 时答案很大)
cpp 复制代码
int main(){
    int n;
    cin >> n;
  • 读入台阶总数 n
cpp 复制代码
    dp[1] = 1, dp[2] = 2, dp[3] = 4;
  • 初始化前三个值(根据实际走法枚举得出)
cpp 复制代码
    for(int i = 4; i <= n; i++)
        dp[i] = dp[i-1] + dp[i-2] + dp[i-3];
  • 从第 4 级开始,按递推公式计算
  • 时间复杂度:O(n),空间复杂度:O(n)
cpp 复制代码
    cout << dp[n];
    return 0;
}
  • 输出走到第 n 级的方案总数

🧪 样例验证

样例 1:n = 4
  • dp[4] = 4 + 2 + 1 = 7 ✅
样例 2:n = 10
  • 依次计算:
    • dp[4]=7
    • dp[5]=13
    • dp[6]=24
    • dp[7]=44
    • dp[8]=81
    • dp[9]=149
    • dp[10]=274 ✅

✅ 总结

要点 说明
模型 三阶线性递推(类斐波那契)
状态转移 dp[i] = dp[i-1] + dp[i-2] + dp[i-3]
初始值 dp[1]=1, dp[2]=2, dp[3]=4
数据类型 用 long long 防止溢出(N=60 时约 10¹² 量级)
复杂度 O(n) 时间,O(n) 空间

补充

在动态规划(DP)中,边界条件(或称初始状态、base case)是整个递推的起点。如果边界处理错误,即使状态转移方程正确,结果也会全错。

下面从 通用原则 + 常见场景 + 实战技巧 三方面讲解如何正确处理 DP 的边界条件。


一、核心原则:"最小不可再分的子问题"

边界条件对应的是问题规模最小、无需递推就能直接得出答案的情况。

✅ 问自己:

"当 n = 0、1、2 ... 时,答案是多少?能否手动枚举?"


二、常见 DP 类型与边界处理方法

1. 线性递推型(如爬楼梯、斐波那契)

  • 问题 :f(n) = f(n-1) + f(n-2) + ...
  • 边界:根据题意手动计算前几项
题目 边界设置 说明
爬楼梯(1/2步) dp[0]=1, dp[1]=1 或 dp[1]=1, dp[2]=2 取决于是否定义 dp[0]
本题(1/2/3步) dp[1]=1, dp[2]=2, dp[3]=4 直接枚举小规模情况
斐波那契 dp[1]=1, dp[2]=1 题目定义

🔑 技巧:优先按题目输入范围的最小值来设边界(如 N≥1,则从 dp1 开始)


2. 网格路径型(如走格子)

  • 状态 :dp[i][j] 表示走到 (i,j) 的方案数
  • 边界:第一行、第一列(只能沿一个方向走)
cpp 复制代码
// 无障碍
for (int i=1; i<=n; i++) dp[i][1] = 1;
for (int j=1; j<=m; j++) dp[1][j] = 1;
  • 有障碍 :若某格是障碍,则 dp[i][j] = 0,且后续路径无法通过它

⚠️ 注意:起点是否合法?

如果 (1,1) 是障碍 → 整个答案为 0


3. 背包问题

  • 01 背包 :cpp

    cpp 复制代码
    dp[0][*] = 0;   // 0 个物品,价值为 0
    dp[*][0] = 0;   // 容量为 0,装不下
  • 完全背包:类似,但注意初始化是否允许"恰好装满"

🔑 关键:dp[0][v] 的含义要明确(选 0 个物品时的最大价值)


4. 区间 DP(如石子合并)

  • 边界 :长度为 1 的区间cpp

    cpp 复制代码
    for (int i=1; i<=n; i++) dp[i][i] = 0; // 合并一个堆,代价为 0

三、处理边界的实用技巧

✅ 技巧 1:从"空状态"开始思考

  • 例如:爬楼梯中,dp[0] = 1 表示"站在地面,有 1 种方式(什么都不做)"
  • 这能让递推更统一(dp[2] = dp[1] + dp[0])

但注意:题目是否允许 n=0? 若输入保证 n≥1,可不设 dp0


✅ 技巧 2:手动枚举小数据验证边界

  • 对 n=1,2,3 手动算出答案
  • 看你的 dp[1], dp[2], dp[3] 是否匹配

例:本题中 N=1 → 1 种;N=2 → 2 种;N=3 → 4 种 → 直接赋值


✅ 技巧 3:数组下标对齐问题

  • 有些题用 dp[0] 表示第 1 个元素,有些用 dp[1]
  • 建议 :让 dp[i] 直接对应"第 i 个"(如台阶 i、第 i 个物品),避免混淆

✅ 技巧 4:警惕"非法状态"

  • 如本题中的"偶行列禁区",某些 dp[i][j] 应强制为 0

  • 在初始化或转移时显式处理:

    cpp 复制代码
    if (isForbidden(i, j)) dp[i][j] = 0;
    else dp[i][j] = dp[i-1][j] + dp[i][j-1];

✅ 技巧 5:使用足够大的数组,避免越界

  • 比如 N≤60,开 dp[65] 而不是 dp[61],防止 i-3 越界
  • 或者在循环中控制起始点(如从 i=4 开始)

四、总结:处理边界四步法

  1. 理解状态定义 :dp[i] 到底表示什么?
  2. 找出最小问题:n=0,1,2... 时答案是多少?
  3. 手动验证:用样例或小数据检查边界值
  4. 统一处理非法状态:障碍、禁区、不可达等设为 0 或 -∞

🎯 一句话记住:

边界条件 = 递推无法覆盖的最小情况 + 非法状态的显式处理

掌握这一点,DP 的正确率会大幅提升!

相关推荐
起个名字费劲死了6 分钟前
VisionMaster集成深度学习算法(基础狗)
人工智能·深度学习·算法
lengxuenong9 分钟前
枚举例题分析
c++·算法·青少年编程
繁华的地方不一定留下你的脚印21 分钟前
C++线程如何安全停止与唤醒:std::jthread、stop_token与condition_variable
开发语言·c++
Navigator_Z27 分钟前
LeetCode //C - 1269. Number of Ways to Stay in the Same Place After Some Steps
c语言·算法·leetcode
2601_9657422230 分钟前
短视频脚本创作方法分享:本地生活类账号的起步思路
大数据·人工智能·算法·ai·新媒体运营·生活
55873生态系统手记39 分钟前
读完 55873 资产钱包的技术栈,我重新理解了 "合规钱包" 应该长什么样
算法·架构
旋生万物42 分钟前
素数螺旋映射 $z_n=n^{1+i}$ 的角分布统计检验与零模型对比
大数据·前端·人工智能·算法·云原生·螺旋生成论·螺旋相位
HRTOS44 分钟前
HRTOS Shell继续完善:优化printf缓冲区与字符串越界保护,国庆后随新版本更新
运维·单片机·嵌入式硬件·算法·51单片机
沫璃染墨1 小时前
《从零入门Linux系统篇(五十一):线程篇·四——pthread线程库详解:从线程创建到终止与分离》
linux·运维·服务器·开发语言·c++·系统架构·线程
shada1 小时前
CMake指定 MSVC 工具集与 Windows SDK
c++·windows