题解:洛谷 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

【核心思想】

  1. 问题分析:给定一个 \(r\) 行的数字三角形,从顶部 \((1,1)\) 出发,每次可向左下方 \((i+1, j)\) 或右下方 \((i+1, j+1)\) 移动,求到达底部任意位置的路径数字和最大值。这是一个经典的动态规划问题,具有最优子结构性质。

  2. 算法选择

    • 自底向上 DP:从倒数第二行开始向上递推,每个位置更新为"当前值 + 左下/右下较大值"
    • 记忆化搜索(DFS):从顶部递归向下,用 \(f[x][y]\) 记录从 \((x,y)\) 到底部的最大路径和,避免重复计算
    • 两种方法等价:自底向上是递推实现,记忆化搜索是递归实现,时间复杂度相同
  3. 关键步骤

    • 读入数据\(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]\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(r^2)\),遍历三角形所有节点一次
    • 空间复杂度:\(O(r^2)\),存储三角形和 DP 数组
  5. 动态规划的核心思想

    • 最优子结构:从 \((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
posted @ 2026-08-03 08:46  团爸讲算法  阅读(32)  评论(0)    收藏  举报