【CSP考题扩展】动态规划入门(1)

这篇具有很好参考价值的文章主要介绍了【CSP考题扩展】动态规划入门(1)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

动态规划(Dynamic Programming,简称DP)是一种在数学、管理科学、计算机科学、经济学和生物信息学等领域中使用的,用来解决特定类型问题的算法思想。其核心思想在于解决复杂问题的过程中,把问题分解为相对简单的子问题,通过解决这些子问题来解决整个问题。以下是动态规划的基本思想和特点的详细介绍:

一、基本思想

动态规划的基本思想是将复杂问题分解成小的、相互关联的子问题,通过解决这些子问题,最终解决原问题。这种方法的关键在于识别出子问题是重叠的,即不同的大问题在解决过程中会重复求解相同的小问题。动态规划通过储存这些子问题的解(通常是在数组或者其他数据结构中),使得每个子问题只被解决一次,从而减少了计算量。

特点

  1. 子问题重叠:在动态规划中,子问题通常不是完全独立的,即一个子问题的解可能会在解决其他子问题时重复使用。这与分治算法的子问题是完全独立的特点不同。

  2. 最优子结构:动态规划所处理的问题应具有最优子结构性质,即问题的最优解包含其子问题的最优解。换句话说,从底层子问题的最优解可以构造出原问题的最优解。

  3. 状态存储:动态规划算法通常使用一个数组或者其他数据结构来存储中间子问题的解,这个存储的结构称为“状态表”或“记忆表”。通过保存这些已经解决的子问题的答案,算法可以避免重复的计算工作。

  4. 状态转移方程:这是动态规划中最核心的概念之一。状态转移方程描述了子问题之间是如何相互转换的。换句话说,它定义了从一个子问题的解如何转变到另一个子问题的解。

  5. 边界条件的定义:在动态规划中,需要明确定义初始条件,即最基本的子问题的解,这有助于算法正确初始化并开始解决问题。

二、DP经典问题

1.数塔问题

问题描述

        7 
      3   8 
    8   1   0 
  2   7   4   4 
4   5   2   6   5
  • 有一个r行的数塔,数塔上有若干数字。问从数塔的最高点到底部,在所有的路径中,经过的数字的和最大为多少?
  • 如上图,是一个5行的数塔,其中7—3—8—7—5的路径经过数字和最大,为30。

解题思路

  1. 朴素递归:从塔顶开始,对每一步可能到达的两个数字进行递归,寻找最大路径。这样的复杂度非常高,为 O ( 2 n ) O(2^n) O(2n),因为有许多重复的子问题被多次计算。

  2. 动态规划的解决方法: 动态规划的方法避免了重复计算相同的子问题。这可以通过创建一个二维数组dp来实现,其中dp[i][j]代表从塔顶到达第i行第j个数字时可能获得的最大总和。

    状态转移方程是:
    d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − 1 ] ) + n u m [ i ] [ j ] dp[i][j] = max(dp[i-1][j], dp[i-1][j-1]) + num[i][j] dp[i][j]=max(dp[i1][j],dp[i1][j1])+num[i][j]

    表明当前位置的最大路径和等于其正上方和左上方两个位置的最大路径和中的较大者加上当前位置的数字。通过这样的方式,我们可以填充整个dp数组,数组的最底行中的最大值就是整个数塔问题的解。

结论

自顶向下的分析帮助我们理解问题和设计算法,而自底向上的计算使我们能够高效地实现算法。这种结合了理论分析和实际计算优势的方法,是动态规划算法设计中的一个核心原则。

1. 自顶向下的分析
  • 问题分解:从最终的目标出发,识别出要解决的问题可以分解为哪些子问题。这有助于我们理解问题的结构和各个子问题之间的关系。
  • 边界识别:明确问题的边界条件,这些是直接可知的解,通常是递归的基准情形。
  • 状态定义:明确状态的定义,即我们需要存储的子问题的解的形式。
  • 转移方程:根据问题的性质,定义状态转移方程,即如何从一个或多个较小的子问题的解得到当前问题的解。

通过自顶向下的分析,我们可以构建出一个清晰的动态规划模型,这是设计动态规划算法的关键步骤。我们通过分析来识别所有必要的组件,如状态和状态转移方程。

2. 自底向上的计算
  • 计算效率:动态规划的一个优势是减少重复计算,自底向上的计算正是通过填充已知的基础情况(边界条件)开始,逐步构建向上的解,确保每个子问题只计算一次。
  • 避免递归调用:虽然自顶向下的递归方法可以解决许多问题,但它可能会导致大量的重复计算和栈溢出。自底向上的迭代方法不需要栈调用,因此可以处理更大的问题实例,且更为高效。
  • 存储优化:在自底向上的计算过程中,我们可以更容易地实施存储优化措施,比如使用滚动数组来节约空间复杂度。

代码实现

#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<vector<int>> num = {
        {7},
        {3, 8},
        {8, 1, 0},
        {2, 7, 4, 4},
        {4, 5, 2, 6, 5}
    };
    
    int n = num.size(); // 塔的层数,可以通过num的大小动态确定
    vector<vector<int>> dp(n, vector<int>(n, 0)); // 创建一个二维dp数组,用于存储到达每个位置的最大路径和

    for (int i = 0; i < n; ++i) // 初始化dp数组的最底层
    {
        dp[n - 1][i] = num[n - 1][i];
    }

    // 自底向上计算路径最大和
    for (int i = n - 2; i >= 0; --i) { // 从倒数第二层开始往上计算
        for (int j = 0; j <= i; ++j) { // 第i层有i+1个数字
            // 计算到达当前位置的最大路径和
            dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]) + num[i][j];
        }
    }

    cout << "The maximum sum path is: " << dp[0][0] << endl;

    return 0;
}

2.数塔问题(扩展)——免费馅饼

问题描述

有一条10米长的小路,以小路起点为x轴的原点,小路终点为x=10,则共有x=0~10共计11个坐标点。(如下图)
【CSP考题扩展】动态规划入门(1),CSP备考,算法,c++,动态规划
接下来的n行每行有两个整数 x , T x,T x,T 表示一个馅饼将在第T秒掉落在坐标 x x x 上。同一秒在同一点上可能掉落有多个馅饼。
初始时你站在 x = 5 x=5 x=5 上,每秒可移动1m,最多可接到所在位置1m范围内某一点上的馅饼。比如你站在 x = 5 x=5 x=5 上,就可以接到 x = 4 , 5 , 6 x=4,5,6 x=4,5,6 其中一点上的所有馅饼。
问你最多可接到多少个馅饼?

样例

6 (表示有6个馅饼)
5 1(在第1s,有一个馅饼掉落在x=5上)
4 1(在第1s,有一个馅饼掉落在x=4上)
6 1
7 2(在第2s,有一个馅饼掉落在x=7上)
7 2(同1s可能有多个馅饼掉落在同一点上)
8 3

样例中最多可接到4个馅饼。其中一种接法是:第1s接住 x = 5 x=5 x=5 的一个馅饼,第2s移动到 x = 6 x=6 x=6,接住 x = 7 x=7 x=7上的两个馅饼,第3s移动到 x = 7 x=7 x=7,接住 x = 8 x=8 x=8的一个馅饼,共计4个馅饼。

解题思路

这个问题可以看作是一种特殊的动态规划问题,与数塔问题相似,不过这次我们不是在寻找从顶部到底部的最大路径,而是寻找能够接到最多馅饼的路径。我们可以将每一秒钟的可能位置看作状态,然后计算每个状态可以接到的最大馅饼数量。

解题思路分为几个步骤:

  1. 状态定义: 定义 d p [ T ] [ x ] dp[T][x] dp[T][x] 表示在第 T T T 秒时,站在位置 x x x 可以接到的最大馅饼数。这是我们要填充的动态规划表。

  2. 状态转移:考虑到每一秒我们可以移动至多1m,因此对于 d p [ T ] [ x ] dp[T][x] dp[T][x],我们可以从 d p [ T − 1 ] [ x − 1 ] dp[T-1][x-1] dp[T1][x1] d p [ T − 1 ] [ x ] dp[T-1][x] dp[T1][x] d p [ T − 1 ] [ x + 1 ] dp[T-1][x+1] dp[T1][x+1] 这三个状态中的任何一个转移过来(如果存在),因为这代表了我们上一秒可能的位置。转移方程为:
    d p [ T ] [ x ] = m a x ( d p [ T − 1 ] [ x − 1 ] , d p [ T − 1 ] [ x ] , d p [ T − 1 ] [ x + 1 ] ) + p i e s [ T ] [ x ] dp[T][x] = max(dp[T-1][x-1], dp[T-1][x], dp[T-1][x+1]) + pies[T][x] dp[T][x]=max(dp[T1][x1],dp[T1][x],dp[T1][x+1])+pies[T][x]
    其中 p i e s [ T ] [ x ] pies[T][x] pies[T][x] 是在第 T T T x x x 位置掉落的馅饼数。

  3. 初始化:初始时刻( T = 0 T=0 T=0)我们站在 x = 5 x=5 x=5 上,所以 d p [ 0 ] [ 5 ] dp[0][5] dp[0][5] 初始为0(假设在第0秒没有馅饼掉落),其他位置初始化为负无穷,代表不可能到达的状态。

  4. 边界情况:在实际计算中,需要考虑边界情况,即当 x = 0 x=0 x=0 x = 10 x=10 x=10 时,我们只能从 x = 1 x=1 x=1 x = 9 x=9 x=9 移动过来,这需要特殊处理。

  5. 填表计算: 按照时间的顺序,从 T = 1 T=1 T=1 开始依次计算 d p [ T ] [ x ] dp[T][x] dp[T][x] 的值,直到最后一秒。

  6. 结果输出:在最后一秒的所有 d p [ T ] [ x ] dp[T][x] dp[T][x] 值中找到最大值,即为我们能接到的最大馅饼数。

这个问题与数塔问题最大的不同在于,数塔问题是一个严格的结构,而这个问题中每一秒的馅饼掉落情况都是动态变化的,我们需要根据输入的数据来动态更新我们的 d p dp dp 表。此外,我们的移动受到限制,每秒只能移动至多1m,这也需要在状态转移时考虑进去。文章来源地址https://www.toymoban.com/news/detail-859920.html

解题代码

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    int n;
    cin >> n; 
    vector<vector<int>> dp(100005, vector<int>(20, 0));
    int maxT = 0; // 记录最大的时间,以知道需要计算的秒数

    for (int i = 0; i < n; ++i) // 读取馅饼信息,并记录到对应的位置和时间上
    {
        int x, T;
        cin >> x >> T;
        dp[T][x + 1]++;
        maxT = max(maxT, T);
    }

    for (int i = maxT - 1; i >= 0; i--) // 自底向上(从倒数第二层开始)
    {
        for (int j = 1; j <= 11; j++) // 无需边界处理,因为边界被初始化为0不影响结果
        {
            dp[i][j] = dp[i][j] + max(max(dp[i + 1][j], dp[i + 1][j + 1]), dp[i + 1][j - 1]); // 三者取最大
        }
    }
    cout << "The maximum number of pies caught is: " << dp[0][6] << endl;

    return 0;
}

到了这里,关于【CSP考题扩展】动态规划入门(1)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【算法思考记录】动态规划入门!力扣2606. 找到最大开销的子字符串【Python3、动态规划】

    原题链接 动态规划(Dynamic Programming,简称 DP)是一种通过将原问题分解为相互重叠的子问题并只解决一次的方法来解决问题的算法优化技术。动态规划通常用于优化递归问题,通过存储子问题的解来避免重复计算,从而显著提高算法的效率。 动态规划的基本思想是将原问题

    2024年02月03日
    浏览(43)
  • 猿创征文 |【算法面试入门必刷】动态规划-线性dp(一)

    📦个人主页:一二三o-0-O的博客 🏆技术方向:C/C++客户端资深工程师(直播+音视频剪辑) 👨‍💻作者简介:数据结构算法与音视频领域创作者 📒 系列专栏:牛客网面试必刷 📣专栏目标:帮助伙伴们通过系统训练,掌握数据结构与算法,收获心仪Offer 📝推荐一个找工作

    2024年02月03日
    浏览(71)
  • 猿创征文 |【算法面试入门必刷】动态规划-线性dp(四)

    📦个人主页:一二三o-0-O的博客 🏆技术方向:C/C++客户端资深工程师(直播+音视频剪辑) 👨‍💻作者简介:数据结构算法与音视频领域创作者 📒 系列专栏:牛客网面试必刷 📣专栏目标:帮助伙伴们通过系统训练,掌握数据结构与算法,收获心仪Offer 📝推荐一个找工作

    2024年02月02日
    浏览(35)
  • 【算法】一文带你快速入门动态规划算法以及动规中的空间优化

    君兮_的个人主页 即使走的再远,也勿忘启程时的初心 C/C++ 游戏开发 Hello,米娜桑们,这里是君兮_,如果给算法的难度和复杂度排一个排名,那么动态规划算法一定名列前茅。在最开始没有什么整体的方法的时候,我也曾经被动态规划折磨过很长时间,通过我一段时间的刷题

    2024年02月05日
    浏览(44)
  • 动态规划入门之线性动态规划

    P1115 最大子段和 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目要求求连续得一段子串使其累加和最大。 我们做动态规划首先考虑小情况,然后推而广之。 假设三个数1,-2,5. 我们先选1然后我们在-2以及-2加1里边选,我们选-1,接着我们在-1以及5里边选我们选择5 由此我们发

    2024年02月12日
    浏览(55)
  • 【动态规划】从入门到实践---动态规划详解

    目录 1.动态规划概念 一.定义数组元素的含义 二.找到数组元素之间的关系表达式 三.找到初始值 2.案例详解 一:爬楼梯 1.定义数组元素的含义 2.找到数组元素之间的关系表达式 3.找到初始值 案例二:最短路径 题目: 做题步骤: 1.定义数组的含义 2.找到数组元素之间的关系表

    2024年02月02日
    浏览(31)
  • 【算法】动态规划 ⑧ ( 动态规划特点 )

    求解类型 : 动态规划 必须是求 最值 , 可行性 , 方案数 , 三者之一 , 如果求其它内容 , 则不能使用动态规划算法 ; 求最值 : 最大值 , 最小值 等 ; 大规模问题的结果 由 小规模问题 的计算结果 取最大值 大规模问题的结果 由 小规模问题 的计算结果 取最小值 可行性 : 是否可行

    2023年04月08日
    浏览(47)
  • 动态规划入门之二维数组的动态规划(过河卒)

    P1002 [NOIP2002 普及组] 过河卒 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 过河卒,首先科普一下象棋里面的马的跳跃一步的规则吧(这题真够坑人的,连个规则都不给出,害得我第一次交就全wa)。一张图解释   大家看所有标记为p的点的坐标就是马跳一步和原来的位置。解决

    2024年02月12日
    浏览(40)
  • 【算法 - 动态规划】原来写出动态规划如此简单!

    从本篇开始,我们就正式开始进入 动态规划 系列文章的学习。 本文先来练习两道通过 建立缓存表 优化解题过程的题目,对如何将 递归函数 修改成 动态规划 的流程有个基本的熟悉。 用最简单的想法完成题目要求的 递归 函数; 定义明确 递归函数 的功能!!! 分析是否存

    2024年02月21日
    浏览(47)
  • 60题学会动态规划系列:动态规划算法第三讲

    简单多状态问题 文章目录 一.按摩师 二.打家劫舍系列 三.删除并获得点数 四.粉刷房子 力扣链接:力扣 一个有名的按摩师会收到源源不断的预约请求,每个预约都可以选择接或不接。在每次预约服务之间要有休息时间,因此她不能接受相邻的预约。给定一个预约请求序列,

    2024年02月08日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包