题解:洛谷 P1216 数字三角形
【题目来源】
洛谷:P1216 [IOI 1994] 数字三角形 Number Triangles - 洛谷
【题目描述】
观察下面的数字金字塔。
写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。

在上面的样例中,7→3→8→7→5 的路径产生了最大权值。
【输入】
第一个行一个正整数 r ,表示行的数目。
后面每行为这个数字金字塔特定行包含的整数。
【输出】
单独的一行,包含那个可能得到的最大的和。
【输入样例】
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
【输出样例】
30
【核心思想】
-
问题分析:给定一个 \(r\) 行的数字三角形,从顶部 \((1,1)\) 出发,每次可向左下方 \((i+1, j)\) 或右下方 \((i+1, j+1)\) 移动,求到达底部任意位置的路径数字和最大值。这是一个经典的动态规划问题,具有最优子结构性质。
-
算法选择:
- 自底向上 DP:从倒数第二行开始向上递推,每个位置更新为"当前值 + 左下/右下较大值"
- 记忆化搜索(DFS):从顶部递归向下,用 \(f[x][y]\) 记录从 \((x,y)\) 到底部的最大路径和,避免重复计算
- 两种方法等价:自底向上是递推实现,记忆化搜索是递归实现,时间复杂度相同
-
关键步骤:
- 读入数据:\(r\) 和三角形数组 \(a[i][j]\)
- 方法一:自底向上递推:
- 从 \(i = r-1\) 到 \(1\) 倒序遍历行
- 对每行 \(j\) 从 \(1\) 到 \(i\):
a[i][j] += max(a[i+1][j], a[i+1][j+1]) - 答案为
a[1][1]
- 方法二:记忆化搜索:
- 初始化 \(f\) 数组为 \(-1\)(未计算标记)
dfs(x, y):若 \(f[x][y] \neq -1\) 直接返回;若 \(x = r\) 返回 \(a[x][y]\);否则 \(f[x][y] = a[x][y] + \max(\text{dfs}(x+1, y), \text{dfs}(x+1, y+1))\)- 答案为 \(f[1][1]\)
-
时间/空间复杂度:
- 时间复杂度:\(O(r^2)\),遍历三角形所有节点一次
- 空间复杂度:\(O(r^2)\),存储三角形和 DP 数组
-
动态规划的核心思想:
- 最优子结构:从 \((i,j)\) 到底部的最大路径和 = \(a[i][j]\) + \(\max\)(从左下出发的最大和, 从右下出发的最大和)
- 无后效性:\((i,j)\) 的最优值只依赖于下一行的两个值,与如何到达 \((i,j)\) 无关
- 状态压缩潜力:自底向上方法可只用一维数组滚动优化,但二维实现更清晰
- 重叠子问题:记忆化搜索中大量状态被重复访问,\(f\) 数组避免重复计算
- 适用于路径最值、树形 DP、递推关系明确的优化问题
【解题思路】

【算法标签】
普及- #递推
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int r, sum=0, mark;
int a[1005][1005]={0};
int main()
{
cin >> r; // 输入r
for (int i=1; i<=r; i++) { // 使用二维数组记录输入的数字
for (int j=1; j<=i; j++) { // 每列长度不一样,注意这里是j<=i
cin >> a[i][j];
}
}
for (int i=r-1; i>=1; i--) { // 使用线性dp方法,从倒数第二行开始计算
for (int j=1; j<=i; j++) {
a[i][j] = max(a[i][j]+a[i+1][j], a[i][j]+a[i+1][j+1]); // 每个元素修改为与左下方相加或与右下角相加的最大值
}
}
cout << a[1][1] << endl; // 最后输出顶端位置的值
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N = 1005; // 定义最大行数
int r; // 三角形的行数
int a[N][N]; // 存储三角形的每个节点的值
int f[N][N]; // 记忆化数组,f[x][y]表示从(x,y)到底部的最大路径和
// 深度优先搜索(DFS)+ 记忆化搜索,计算从(x,y)到底部的最大路径和
int dfs(int x, int y)
{
// 如果已经计算过,直接返回结果
if (f[x][y] != -1) return f[x][y];
// 如果当前是最后一行,直接返回当前节点的值
if (x == r)
{
f[x][y] = a[x][y];
}
else
{
// 递归计算从下一行的两个相邻节点到底部的最大路径和,并加上当前节点的值
f[x][y] = a[x][y] + max(dfs(x + 1, y), dfs(x + 1, y + 1));
}
return f[x][y]; // 返回从(x,y)到底部的最大路径和
}
int main()
{
cin >> r; // 输入三角形的行数
for (int i = 1; i <= r; i++)
for (int j = 1; j <= i; j++)
cin >> a[i][j]; // 输入三角形的每个节点的值
memset(f, -1, sizeof(f)); // 初始化记忆化数组为-1,表示未计算
dfs(1, 1); // 从三角形的顶部开始计算最大路径和
cout << f[1][1] << endl; // 输出从顶部到底部的最大路径和
return 0;
}
【运行结果】
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
30
浙公网安备 33010602011771号