动态规划2(数塔问题)

数塔问题是二维情况下动态规划的经典问题,下面以洛谷的一个例题来分析数塔问题以及动态规划:原题链接

题目描述

观察下面的数字金字塔。写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。在这里插入图片描述
在上面的样例中,从7→3→8→7→5 的路径最大

输入格式

第一个行一个正整数 rr ,表示行的数目。后面每行为这个数字金字塔特定行包含的整数。

输出格式

单独的一行,包含那个可能得到的最大的和。

样例输入

5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5

样例输出

30

数据范围

对于100%的数据,1 ≤ r ≤ 1000,所有输入在 [0,100] 范围内。

数塔问题递归写法分析:

  • 可以使用一个二维数组存储数塔,需要注意的是,在本题的数塔里,路径可以选择向左或者向右,但是若将数塔存储在二维数组中,数塔路径的选择只能是向下向右下这两个方向
  • 在二维数组中,当前位置的下方是[i + 1][ j ],右下方是[i +1][j + 1]
  • 本题样例中给出的是5层数塔,可以用dfs(1,1)表示从第1行第1列到第5行的最大路径,而想要找到答案,需要先寻找第2行到第5行的最大路径,即将5层数塔的最长路径转化为了4层数塔的最长路径这个子问题。即max{dfs(2,1),dfs(2,2)} + a[1][1]… … 以此类推
  • 当dfs()函数递归到最后1行(x == n)时,如数塔一共5行,目前正在执行dfs(5,1),则直接返回数塔中的第5行第1列的数字a[5][1]即可,之后递归函数会依次向上继续返回

递归写法代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;
int a[N][N];//存储数据
int n;//递归函数中需要设置边界,故将n设为全局变量

int dfs(int x,int y)//x表示行,y表示列
{
    if (x == n) return a[x][y];//递归边界
    return max(dfs(x + 1,y),dfs(x + 1,y + 1)) + a[x][y];
}

int main()
{
    cin >> n;
    
    for (int i = 1;i <= n;i++)//读入数塔:数塔有n行
        for (int j = 1;j <= i;j++)//每行有n列
            cin >> a[i][j];
    
    cout << dfs(1,1) << endl;//dfs(1,1):从第1行第1列到边界的最大路径
    
    return 0;
}

当然,当数据范围很大时,由于递归写法的重叠子问题太多,所以又见到了熟悉的TLE… …话不多说直接上图在这里插入图片描述
下面给出本题的AC代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;
int a[N][N];//存储数塔
int f[N][N];//表示从第i行第j列到第n行的最大路径
int n;

int main()
{
    while (cin >> n)
    {
        for (int i = 1;i <= n;i++)
            for (int j = 1;j <= i;j++)
                cin >> a[i][j];
                
        for (int i = n;i >= 1;i--)//用i来表示行,且从最后一行开始
            for (int j = 1;j <= i;j++)//每行有i列,且从第一列开始
                f[i][j] = max(f[i + 1][j],f[i + 1][j + 1]) + a[i][j];//自底向上计算最长路径
                
        cout << f[1][1] << endl;//当for循环结束完毕之后f[1][1]:从数塔第1行第1列开始的最长路径
    }
    return 0;
}

动态规划的基本思想:将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。我们需要保存已解决的子问题的答案,这样可以避免大量的重复计算,节省时间。我们会用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中,而在需要时再找出已求得的答案。

换个新数塔哈~
在这里插入图片描述

如上图所示,若想找到这个8层数塔的最长路径,即将问题描述为:

  • 从第一行第一列的数字7开始,找出一条最长路径。
    很明显,若想解决上面的问题,有两条路径可以选择:
    从数字7下面的0开始寻找路径(子问题)
    从数字7右下的11开始寻找路径(子问题)

以此类推,不难发现,从红色位置开始找出一条最长路径,这个问题依赖于很多个子问题,而这些子问题构成了绿色部分。

我们只需要计算出①②两个子问题的答案,选择其中较大的值,再加上第一行第一列的那个数字,就可以得到数塔的最长路径。

由以上分析,得出状态转移方程

dp[i][j] = max{dp[i + 1][j],dp[i + 1][j + 1]} + a[i][j]
边界:当i > n时,f[i][j] = 0;

下面再给出数塔的一个例题
原题链接

AC代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;
int a[N][N];//存储数塔
int f[N][N];//表示从第i行第j列到第n行的最大路径
int n;

int main()
{
    int c;
    cin >> c;
    
    while (c--)
    {
        cin >> n;
        for (int i = 1;i <= n;i++)
            for (int j = 1;j <= i;j++)
                cin >> a[i][j];
                
        for (int i = n;i >= 1;i--)//用i来表示行,且从最后一行开始
            for (int j = 1;j <= i;j++)//每行有i列,且从第一列开始
                f[i][j] = max(f[i + 1][j],f[i + 1][j + 1]) + a[i][j];//自底向上计算最长路径
                
        cout << f[1][1] << endl;//当for循环结束完毕之后f[1][1]:从数塔第一层开始的最长路径
    }
    return 0;
}
  • 11
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Data Structures, Algorithms, and Applications in C++, Second Edition 出版者的话 译者序 前言 第一部分 预备知识 第1章 C++回顾 1.1 引言 1.2 函数与参数 1.2.1 传值参数 1.2.2 模板函数 1.2.3 引用参数 1.2.4 常量引用参数 1.2.5 返回值 1.2.6 重载函数 1.3 异常 1.3.1 抛出异常 1.3.2 处理异常 1.4 动态存储空间分配 1.4.1 操作符new 1.4.2 一维数组 1.4.3 异常处理 1.4.4 操作符delete 1.4.5 二维数组 1.5 自有数据类型 1.5.1 类currency 1.5.2 一种不同的描述方法 1.5.3 操作符重载 1.5.4 友元和保护性类成员 1.5.5 增加#ifndef、#define和#endif语句 1.6 异常类illegalParameterValue 1.7 递归函数 1.7.1 递归的数学函数 1.7.2 归纳 1.7.3 C++递归函数 1.8 标准模板库 1.9 测试与调试 1.9.1 什么是测试 1.9.2 测试数据的设计 1.9.3 调试 1.10 参考及推荐读物 第2章 程序性能分析 2.1 什么是程序性能 2.2 空间复杂度 2.2.1 空间复杂度的组成 2.2.2 举例 2.3 时间复杂度 2.3.1 时间复杂度的组成 2.3.2 操作计数 2.3.3 最好、最坏和平均操作计数 2.3.4 步数 第3章 渐近记法 3.1 引言 3.2 渐近记法 3.2.1 大Ο记法 3.2.2 渐近记法Ω和Θ 3.3 渐近数学(可选) 3.3.1 大O记法 3.3.2 Ω记法 3.3.3 Θ记法 3.3.4 小ο记法 3.3.5 特性 3.4 复杂度分析举例 3.5 实际复杂度 3.6 参考及推荐读物 第4章 性能测量 4.1 引言 4.2 选择实例的大小 4.3 设计测试数据 4.4 实验设计 4.5 高速缓存 4.5.1 简单计算机模型 4.5.2 缓存未命中对运行时间的影响 4.5.3 矩阵乘法 4.6 参考及推荐读物 第二部分 数据结构 第5章 线性表——数组描述 5.1 数据对象和数据结构 5.2 线性表数据结构 5.2.1 抽象数据类型linearList 5.2.2 抽象类linearList 5.3 数组描述 5.3.1 描述 5.3.2 变长一维数组 5.3.3 类arrayList 5.3.4 C++迭代器 5.3.5 arrayList的一个迭代器 5.4 vector的描述 5.5 在一个数组中实现的多重表 5.6 性能测量 5.7 参考及推荐读物 第6章 线性表——链式描述 6.1 单向链表 6.1.1 描述 6.1.2 结构chainNode 6.1.3 类chain 6.1.4 抽象数据类型linearList的扩充 6.1.5 类extendedChain 6.1.6 性能测量 6.2 循环链表和头节点 6.3 双向链表 6.4 链表用到的词汇表 6.5 应用 6.5.1 箱子排序 6.5.2 基数排序 6.5.3 凸包 6.5.4 并查集 第7章 数组和矩阵 7.1 数组 7.1.1 抽象数据类型 7.1.2 C++数组的索引 7.1.3 行主映射和列主映射 7.1.4 用数组的数组来描述 7.1.5 行主描述和列主描述 7.1.6 不规则二维数组 7.2 矩阵 7.2.1 定义和操作 7.2.2 类matrix 7.3 特殊矩阵 7.3.1 定义和应用 7.3.2 对角矩阵 7.3.3 三对角矩阵 7.3.4 三角矩阵 7.3.5 对称矩阵 7.4 稀疏矩阵 7.4.1 基本概念 7.4.2 用单个线性表描述 7.4.3 用多个线性表描述 7.4.4 性能测量 第8章 栈 8.1 定义和应用 8.2 抽象数据类型 8.3 数组描述 8.3.1 作为一个派生类实现 8.3.2 类arrayStack 8.3.3 性能测量 8.4 链表描述 8.4.1 类derivedLinkedStack 8.4.2 类linkedStack 8.4.3 性能测量 8.5 应用 8.5.1 括号匹配 8.5.2 汉诺塔 8.5.3 列车车厢重排 8.5.4 开关盒布线 8.5.5 离线等价类问题 8.5.6 迷宫老鼠 8.6 参考及推荐读物 第9章 队列 9.1 定义和应用 9.2 抽象数据类型 9.3 数组描述 9.3.1 描述 9.3.2 类arrayQueue 9.4 链表描述 9.5 应用 9.5.1 列车车厢重排 9.5.2 电路布线 9.5.3 图元识别 9.5.4 工厂仿真 9.6 参考及推荐读物 第10章

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值