一本通1258.数字金字塔 题解 动态规划

题目链接:http://ybt.ssoier.cn:8088/problem_show.php?pid=1258

题目大意:

给你一个数字金字塔,每次可以从当前点走到左下方或右下方的点。查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。

解题思路:

(a_{i,j}) 表示数字金字塔上第 (i) 行从左往右数第 (j) 个点,则我们的目的就是从 (a_{1,1}) 走到第 (n) 行的 (a_{n,i})(此处 (i) 可能是 (1 sim n) 范围内的任何一个点)。

很明显这道题目可以用动态规划(DP)求解,但是根据求解的方向不同,对应的状态也不相同。

这里,我们可以以两种思路来解决这个问题:

  • 第1种思路:自顶向下(从上往下推);
  • 第2种思路:自底向上(从下往上推)。

下面,我们来依次分析两种思路。

自顶向下(从上往下推)

定义状态 (f_{i,j}) 表示从 (a_{1,1}) 走到 (a_{i,j}) 的所有路径数字和的最大值。则,可以推导出状态转移方程为:

  • (i =1) 时((i=1) 时只有一个状态 (f_{1,1})),(f_{1,1} = a_{1,1})
  • (i gt 1) 时,
    • (j = 1) 时,(f_{i,1} = f{i-1,1} + a_{i,1})(最左边的点只能从右上方走过来);
    • (j = i) 时,(f_{i,i} = f{i-1,i-1} + a_{i,i})(最右边的点只能从左上方走过来);
    • (1 lt j lt i) 时,(f_{i,j} = max{f_{i-1,j-1}f_{i-1,j}} + a_{i,j})(中间的点可以选择从左上角或右上角的点走过来,所以选择两者的较大值)

(i = 1 ightarrow n) 的顺序推导出所有的 (f_{i,j}),则最终的答案就是:

  • (f_{n,1})(从 (a_{1,1}) 走到 (a_{n,1}) 的最大数字和)、
  • (f_{n,2})(从 (a_{1,1}) 走到 (a_{n,2}) 的最大数字和)、
  • (f_{n,3})(从 (a_{1,1}) 走到 (a_{n,3}) 的最大数字和)、
  • …… ……
  • (f_{n,n})(从 (a_{1,1}) 走到 (a_{n,n}) 的最大数字和)

的最大值,即答案为 (max{ f_{n,i} })(其中 (i in [1,n])

说明:(in) 是“属于”符号,(i in [1,n]) 表示 (i) 属于 ([1,n]) 范围内,即 (i)(1)(n) 范围内(包括 (1)(n))的一个整数。

按照自顶向下实现的代码如下:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1010;
int n, a[maxn][maxn], f[maxn][maxn], ans;
int main()
{
    scanf("%d", &n);    // 虽然题目里用R表示行的数量,但是我还是比较习惯用n表示行数
    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= i; j ++)
            scanf("%d", &a[i][j]);
    for (int i = 1; i <= n; i ++)
    {
        for (int j = 1; j <= n; j ++)
        {
            if (i == 1) f[i][j] = a[i][j];
            else if (j == 1) f[i][j] = f[i-1][j] + a[i][j];
            else if (j == i) f[i][j] = f[i-1][j-1] + a[i][j];
            else f[i][j] = max(f[i-1][j-1], f[i-1][j]) + a[i][j];
        }
    }
    for (int i = 1; i <= n; i ++)
        ans = max(ans, f[n][i]);
    printf("%d
", ans);
    return 0;
}

自底向上(从下往上推)

从第 (1) 行的格子((a_{1,1}))走到第 (n) 行的路径的最大数字和,等价于从第 (n) 行选一个点作为起点从下往上走到 (a_{1,1}) 的路径最大数字和。所以我们可以把问题看成从下往上走找一条最大路径的数字和。

那么我们可以定义状态 (f_{i,j}) 表示:从第 (n) 行找一个点作为起点走到 (a_{i,j}) 的路径的最大数字和,则可以推导出转台转移方程为:

  • (i = n) 时,(f_{i,j} = a_{i,j})(因为时最下面一行,起点出发能够得到的数字就是本身);
  • (i lt n) 时,(f_{i,j} = max{ f_{i+1,j} , f_{i+1,j+1} } + a_{i,j})

注意这里要按照 (i = n ightarrow 1) 的方向推导出所有的状态。

则最终的状态为 (f_{1,1})(因为从小往上推的话,所有路径对应的重点只有一个就是 (a_{1,1}))。

按照自底向上实现的代码如下(可以发现,按照这种方法实现的代码更简洁一些):

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1010;
int n, a[maxn][maxn], f[maxn][maxn];
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= i; j ++)
            scanf("%d", &a[i][j]);
    for (int i = n; i >= 1; i --)
    {
        for (int j = 1; j <= i; j ++)
        {
            if (i == n) f[i][j] = a[i][j];
            else f[i][j] = max(f[i+1][j], f[i+1][j+1]) + a[i][j];
        }
    }
    printf("%d
", f[1][1]);
    return 0;
}
原文地址:https://www.cnblogs.com/quanjun/p/15134297.html