1. 下楼梯
解法 :
因为上楼和下楼是⼀个可逆的过程,因此我们可以把下楼问题转化成上到第n个台阶,⼀共有多少种方案。
- 状态表⽰: dp [ i ] 表⽰:⾛到第 i 个台阶的总⽅案数。 那最终结果就是在 dp [ n ] 处取到。
- 状态转移⽅程: 根据最后⼀步划分问题,⾛到第 i 个台阶的⽅式有三种:
从 i − 1 台阶向上⾛ 1 个台阶,此时⾛到 i 台阶的⽅案就是 dp [ i − 1] ;
从 i − 2 台阶向上⾛ 2 个台阶,此时⾛到 i 台阶的⽅案就是 dp [ i − 2] ;
从 i − 3 台阶向上⾛ 3 个台阶,此时⾛到 i 台阶的⽅案就是 dp [ i − 3] ;
综上所述, dp[i] = dp[i− 1] + dp[i− 2] + dp[i− 3] 。 - 初始化: 填位置的值时,⾄少需要前三个位置的值,因此需要初始化,然后从i = 3开始填。
dp [0] = 1, dp [1] = 1, dp [2] = 2,或者初始化 dp [1] = 1, dp [2] = 2, dp [3] = 4 ,然后从 i = 4 开始填。 - 填表顺序 : 从左往右。
代码:
cpp
#include<iostream>
using namespace std;
typedef long long LL;
const int N = 60;
int n;
LL dp[N]; //f[i]表示当有i个台阶时,一共有多少种方案
int main()
{
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] << endl;
return 0;
}
动态规划的空间优化:
我们发现,在填写dp[i]的值时,我们仅仅需要前三个格⼦的值,第 个及其之前的格⼦的值已经毫⽆⽤处了。因此,可以⽤三个变量记录i位置之前三个格⼦的值,然后在填完i位置的值之后,滚动向后更新。

cpp
#include<iostream>
using namespace std;
typedef long long LL;
const int N = 60;
int n;
int main()
{
cin >> n;
//初始化
LL a = 1, b = 1, c = 2;
for (int i = 3;i <= n;i++)
{
LL t = a + b + c;
a = b;
b = c;
c = t;
}
if (n == 1)
cout << b << endl;
else
cout << c << endl;
return 0;
}
2. 数字三角形
cpp
#include<iostream>
using namespace std;
const int N = 1010;
int r;
int a[N][N]; //原始数字的数组
int dp[N][N]; //相加之后的数字的数组
int main()
{
cin >> r; //一共有r行
for (int i = 1;i <= r;i++)
{
for (int j = 1;j <= i;j++) //一定是j<=i而不是j<=r
{
cin >> a[i][j];
}
}
for (int i = 1;i <= r;i++)
{
for (int j = 1;j <= i;j++)
{
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1]) + a[i][j];
}
}
int ret = 0;
for (int j = 1;j <= r;j++)
{
ret = max(ret,dp[r][j]); //在第r行的第j列中通过max筛选出最大的数字
}
cout << ret << endl;
return 0;
}
解法:
1. 状态表示 : dp[i][j] 表示: 走到 [i,j] 位置的最大权值。那最终结果就是在 dp 表的第 n 行中, 所有元素的最大值。
2. 状态转移方程 : 根据最后一步划分问题, 走到 [i,j] 位置的方式有两种:
a. 从 [i-1,j] 位置向下走一格, 此时走到 [i,j] 位置的最大权值就是 dp[i-1][j] ;
b. 从 [i-1,j-1] 位置向右下走一格, 此时走到 [i,j] 位置的最大权值就是 dp[i-1][j-1] ;
综上所述, 应该是两种情况的最大值再加上 [i,j] 位置的权值:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-1]) + a[i][j] 。
3. 初始化 : 因为 dp 表被 0 包围着, 并不影响我们的最终结果, 因此可以直接填表。
4. 填表顺序 : 从左往右填写每一行, 每一行从左往右。
思考, 如果权值出现负数的话, 需不需要初始化?
此时可以全都初始化为 -∞ , 负无穷大在取 max 之后, 并不影响最终结果。
代码:
cpp
#include<iostream>
using namespace std;
const int N = 1010; //防止数组越界
int r;
int a[N][N]; //原始数字的数组
int dp[N][N]; //数字相加后的数组,也就是最终数组
int main()
{
cin >> r;
for (int i = 1;i <= r;i++)
{
for (int j = 1;j <= i;j++) //一定是j<=i而不是j<=r
{
cin >> a[i][j];
}
}
for (int i = 1;i <= r;i++)
{
for (int j = 1;j <= i;j++)
{
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1]) + a[i][j];
//状态转移方程:当前位置的结果只能通过自己前面和
//左前方的数字两个中较大的数组再加上自己本身得到
}
}
int ret = 0;
for (int j = 1;j <= r;j++)
{
ret = max(ret,dp[r][j]); //在第r行的第j列通过max筛选出最大的数字
}
cout << ret << endl;
return 0;
}
动态规划的空间优化:
我们发现, 在填写第 i 行的值时, 我们仅仅需要前一行的值, 并不需要第 i-2 以及之前行的值。
因此, 我们可以只用一个一维数组来记录上一行的结果, 然后在这个数组上更新当前行的值。

cpp
#include<iostream>
using namespace std;
const int N = 1010;
int r;
int a[N][N];
int dp[N]; //优化后是一位数组
int main()
{
cin >> r;
for (int i = 1;i <= r;i++)
{
for (int j = 1;j <= i;j++)
{
cin >> a[i][j];
}
}
// 空间优化
for (int i = 1; i <= r; i++)
{
for (int j = i; j >= 1; j--) // 修改⼀下遍历顺序
{
dp[j] = max(dp[j], dp[j - 1]) + a[i][j];
}
}
int ret = 0;
for (int j = 1;j <= r;j++)
{
ret = max(ret, dp[j]);
}
cout << ret << endl;
return 0;
}
3. 台阶问题

斐波那契数列模型
解法:
- 状态表⽰: dp [ i ] 表⽰:⾛到 i 位置的⽅案数。 那么 dp [ n ] 就是我们要的结果。
- 状态转移⽅程: 可以从 i − k ≤ j ≤ i − 1 区间内的台阶⾛到 i 位置,那么总⽅案数就是所有的dp [ j ] 累加在⼀ 起。 注意 i− k 不能⼩于 0 。
- 初始化: dp [0] = 1 ,起始位置,为了让后续填表有意义。
- 填表顺序: 从左往右。
代码:
cpp
#include<iostream>
using namespace std;
const int N = 1e5 + 10, MOD = 1e5 + 3;
int n, k;
int dp[N];
int main()
{
cin >> n >> k;
dp[0] = 1;
for (int i = 1;i <= n;i++)
{
for (int j = 1;j <= k && i-j> = 0;j++)
{
dp[i] = (dp[i] + dp[i - j]) % MOD;
}
}
cout << dp[n] << endl;
return 0;
}
总结
本文介绍了三个动态规划问题的解法。首先讨论下楼梯问题,通过状态转移方程dp[i]=dp[i-1]+dp[i-2]+dp[i-3]计算到第n个台阶的方案数,并给出空间优化方案。其次分析数字三角形问题,使用dp[i][j]=max(dp[i-1][j],dp[i-1][j-1])+a[i][j]求最大路径和,同样提供空间优化方法。最后是台阶问题的斐波那契数列模型解法,通过累加前k步的方案数求解。每个问题都详细说明了状态表示、转移方程、初始化和填表顺序,并给出了对应的C++代码实现。