【题目描述】
观察下面的数字金字塔。写一个程序查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以从当前点走到左下方的点也可以到达右下方的点。
在上面的样例中,从13到8到26到15到24的路径产生了最大的和86。
【输入】
第一个行包含R(1≤R≤1000),表示行的数目。
后面每行为这个数字金字塔特定行包含的整数。
所有的被供应的整数是非负的且不大于100。
【输出】
单独的一行,包含那个可能得到的最大的和。
【输入样例】
5
13
11 8
12 7 26
6 14 15 8
12 7 13 24 11
【输出样例】
86
解题思路:解决这个问题,可以使用动态规划(Dynamic Programming,DP)的方法。动态规划是解决最优化问题的一个有效方法,特别适用于有重叠子问题和最优子结构性质的问题。
策略:1.从上到下计算,2.从下到上计算
在这个问题中,已知的信息是金字塔的底部,未知的信息是到达顶部的路径和。如果我们从顶部开始计算,就需要考虑所有可能的路径,这会导致大量的重复计算和更高的时间复杂度。所以选择2从下到上计算。
思路分析:首先,需要建立一个二维数组来存储金字塔的每一层数字。然后,从金字塔的底部开始向上计算,每一步都保留到达该位置的最大路径和。
1.状态设计:a[i][j]表示从最下行到第i行第j列的最大值。
2.状态转移方程:a[i][j] += max(a[i + 1][j], a[i + 1][j + 1])。
a[i][j]
在开始时存储了金字塔该位置上的数字,但在动态规划的过程中,它被更新为从该位置到底部的最大路径和。因此,当我们计算 a[i][j] += max(a[i + 1][j], a[i + 1][j + 1])
时,实际上是将从 (i + 1, j)
和 (i + 1, j + 1)
两个位置到底部的最大路径和中较大的一个,加上当前位置 a[i][j]
的值,来更新 a[i][j]
。
最终,当所有的状态都更新完毕后,a[1][1]
就存储了从金字塔顶部到底部的最大路径和。最后输出这个值即可。
以下是使用文本表示的简化表格,展示了动态规划的过程:
如下的数字金字塔
3 | |
7 4 | |
2 4 6 | |
8 5 9 3 |
将通过动态规划填充下面的表格,其中每个单元格(i, j)
表示从金字塔的顶部到位置(i, j)
的最大路径和。注意,在表格中,我们从第1行开始计数,以便与数组索引保持一致(实际代码中通常从第1行开始计数)。
初始表格(只包含金字塔的数值):
1 | 3 | |
2 | 7 4 | |
3 | 2 4 6 | |
4 | 8 5 9 3 |
动态规划填充表格的过程(每一步计算最大路径和并更新表格):
第一步(从倒数第二层开始,即第3行):
1 | 3 | |
2 | 7 4 | |
3 | 15 10 13 <- 更新这一行:7+8, 4+5, 4+9 | |
4 | 8 5 9 3 |
第二步(继续向上到第2行):
1 | 3 | |
2 | 22 14 <- 更新这一行:15+7, max(10,13)+4 | |
3 | 15 10 13 | |
4 | 8 5 9 3 |
第三步(到达顶部,即第1行):
1 | 25 <- 更新这个单元格:max(22,14)+3 | |
2 | 22 14 | |
3 | 15 10 13 | |
4 | 8 5 9 3 |
最终,表格顶部的单元格就包含了从金字塔顶部到底部的最大路径和,即25。
在实际代码中,不会创建一个额外的表格来存储中间结果,而是直接在原数组a
上进行更新。上面的表格提供了一个可视化的方法来理解动态规划是如何逐步计算最大路径和的。每一步都依赖于前一步的计算结果,并且避免了重复计算相同子问题的解,这是动态规划的关键优势
#include <iostream>
#include <algorithm>
using namespace std;
const int MAXN = 1005;
int a[MAXN][MAXN];
int main() {
int R;
cin >> R; // 输入行数
for (int i = 1; i <= R; i++)
for (int j = 1; j <= i; j++)
cin >> a[i][j]; // 输入数字金字塔
for (int i = R - 1; i >= 1; i--)
for (int j = 1; j <= i; j++)
a[i][j] += max(a[i + 1][j], a[i + 1][j + 1]); // 动态规划计算最大和
cout << a[1][1] << endl; // 输出结果
return 0;
}