• 一本通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;
    }
    
  • 相关阅读:
    2
    1
    Java面试题整理二(侧重SSH框架)
    solr添加多个core
    Oracle SQL性能优化
    jQuery的$.ajax
    spring四种依赖注入方式
    通过JAXB完成Java对象与XML之间的转换
    window环境下将solr6.3部署到tomcat中
    Java面试题整理一(侧重多线程并发)
  • 原文地址:https://www.cnblogs.com/quanjun/p/15134297.html
Copyright © 2020-2023  润新知