线性 DP 概述
线性动态规划是算法竞赛和面试中的基础题型。它的核心在于状态转移只依赖于前一个或前几个状态,通常可以用一维或二维数组来存储中间结果。我们在入门阶段遇到的《下楼梯》和《数字三角形》本质上都是线性 DP 的变体。
下面通过四道经典题目,梳理线性 DP 的状态定义、转移方程推导以及边界处理的实战技巧。
台阶问题
题目描述
给定 n 个台阶,每次可以走 1 到 k 步,求走到第 n 个台阶的方案数。结果对 100003 取模。
思路分析
这道题是典型的一维 DP。我们定义 dp[i] 为走到第 i 个台阶的方案总数。对于第 i 个台阶,可以从 i-1, i-2, ..., i-k 处跳过来。因此,状态转移方程就是累加这些前驱状态的值。
这里有两个细节需要注意:
- 取模运算:由于方案数增长很快,每一步加法后都要取模,防止溢出。
- 边界检查:当 i < k 时,i-j 可能小于 0,循环中需要判断
i - j >= 0。
关于初始化,很多初学者会尝试将前 k 个位置单独赋值,其实更简洁的做法是将 dp[0] 设为 1。这代表站在起点(第 0 阶)有一种方案(不动),这样从 dp[1] 开始递推就能自然覆盖所有情况。
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10, MOD = 1e5 + 3;
int n, k;
LL dp[N];
int main() {
cin >> n >> k;
// 初始化:起点算作一种方案
dp[0] = 1;
// 循环填表
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= k; j++) {
if (i - j < 0) break; // 保证不越界
dp[i] = (dp[i] + dp[i - j]) % MOD;
}
}
cout << dp[n] << endl;
return ;
}


