热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

NOIP竞赛学习整理动态规划算法举例P1264

动态规划什么是动态规划?动态规划是解决多阶段决策最优化问题的一种思想方法。所谓“动态”,指的是在问题的多阶段决策中,按某一顺序

动态规划


什么是动态规划?

动态规划是解决多阶段决策最优化问题的一种思想方法。所谓“动态”,指的是在问题的多阶段决策中,按某一顺序,根据每一步所选决策的不同,将随即引起状态的转移,最终在变化的状态中产生一个决策序列。动态规划就是为了使产生的决策序列在符合某种条件下达到最优。


一、动态规划中的主要概念,名词术语

1 阶段:把问题分成几个相互联系的有顺序的几个环节,这些环节即称为阶段。
2 状态:某一阶段的出发位置称为状态。通常一个阶段包含若干状态。如图1中,阶段3就有三个状态结点4、5、6。
3 决策:从某阶段的一个状态演变到下一个阶段某状态的选择。
4策略:由开始到终点的全过程中,由每段决策组成的决策序列称为全过程策略,简称策略。
5 状态转移方程:前一阶段的终点就是后一阶段的起点,前一阶段的决策选择导出了后一阶段的状态,这种关系描述了由k阶段到k+1阶段状态的演变规律,称为状态转移方程。
6 目标函数与最优化概念:目标函数是衡量多阶段决策过程优劣的准则。最优化概念是在一定条件下找到一个途径,经过按题目具体性质所确定的运算以后,使全过程的总效益达到最优。


二、动态规划问题的数学描述

首先,例举一个典型的且很直观的多阶段决策问题:
[例] 下图表示城市之间的交通路网,线段上的数字表示费用,单向通行由
A->E。试用动态规划的最优化原理求出 A->E 的最省费用。

在这里插入图片描述

如图从 A 到 E 共分为 4 个阶段,即第一阶段从 A 到 B,第二阶段从 B 到 C,
第三阶段从 C 到 D,第四阶段从 D 到 E。除起点 A 和终点 E 外,其它各点既是上
一阶段的终点又是下一阶段的起点。例如从 A 到 B 的第一阶段中,A 为起点,终
点有 B1,B2,B3 三个,因而这时走的路线有三个选择,一是走到 B1,一是走到
B2,一是走到 B3。若选择 B2 的决策,B2 就是第一阶段在我们决策之下的结果,
它既是第一阶段路线的终点,又是第二阶段路线的始点。在第二阶段,再从 B2
点出发,对于 B2 点就有一个可供选择的终点集合(C1,C2,C3);若选择由 B2
走至 C2 为第二阶段的决策,则 C2 就是第二阶段的终点,同时又是第三阶段的始
点。同理递推下去,可看到各个阶段的决策不同,线路就不同。很明显,当某阶
段的起点给定时,它直接影响着后面各阶段的行进路线和整个路线的长短,而后 面各阶段的路线的发展不受这点以前各阶段的影响。故此问题的要求是:在各个
阶段选取一个恰当的决策,使由这些决策组成的一个决策序列所决定的一条路
线,其总路程最短。


三、 运用动态规划需符合的条件

任何思想方法都有一定的局限性,超出了特定条件,它就失去了作用。同理,动态规划也并不是万能的。那么使用动态规划必须符合什么条件呢?必须满足最优化原理和无后效性。

1 最优化原理

最优化原理可这样阐述:一个最优化策略具有这样的性质,不论过去状
态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。简而言之,一个最优化策略的子策略总是最优的。

2 无后效性

“过去的步骤只能通过当前状态影响未来的发展,当前的状态是历史的总结”。这条特征说明动态规划只适用于解决当前决策与过去状态无关的问题。状态,出现在策略任何一个位置,它的地位相同,都可实施同样策略,这就是无后效性的内涵。
由上可知,最优化原理,无后效性,是动态规划必须符合的两个条件。


四 、动态规划的计算方法

对于一道题,怎样具体运用动态规划方法呢?

(1)首先,分析题意,考察此题是否满足最优化原理与无后效性两个条件。
(2)接着,确定题中的阶段,状态,及约束条件。
(3)推导出各阶段状态间的函数基本方程,进行计算。


应用举例:数字三角形(洛谷-P1216)

题目描述
观察下面的数字金字塔。

写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。

7 3 8
8 1 0

2 7 4 4
4 5 2 6 5
在上面的样例中,从 7 \to 3 \to 8 \to 7 \to 57→3→8→7→5 的路径产生了最大

输入格式
第一个行一个正整数 rr ,表示行的数目。

后面每行为这个数字金字塔特定行包含的整数。

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

输入输出样例
输入:
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输出
30

思路:

@顺推:

F[i+1][j] = MAX (F[i][j] + a[i+1][j]);

F[i+1][j+1] = MAX (F[i][j] + a[i+1][j+1]);

@ 逆推:

F[i][j] = MAX (F[i-1][j], F[i-1][j-1]) + a[i][j]; (注意!逆推时要注意边界情况! )

代码如下:
1、顺推:

//T2:数字金字塔-顺推(有点类似于记忆化搜索的思路)
//d数组储存顺序:记录从顶端向底部走的路径最优值(自顶向下)#include
#include
using namespace std;
int a[1005][1005];//储存数塔
int d[1005][1005];//从该点到底端的最大数字和
int main()
{int i,j,n,ans;while(cin>>n){memset(d,-1,sizeof(d));for(i&#61;0;i<n;i&#43;&#43;)for(j&#61;0;j<&#61;i;j&#43;&#43;){cin>>a[i][j];}d[0][0]&#61;a[0][0];for(int i&#61;0;i<n-1;&#43;&#43;i)for(int j&#61;0;j<&#61;i;&#43;&#43;j)//d数组为最优值路径&#xff08;黑色金字塔&#xff0c;a为源数据数组&#xff08;紫色金字塔&#xff09;{//分别用最优值来更新左下方和右下方d[i&#43;1][j]&#61;max(d[i&#43;1][j],d[i][j]&#43;a[i&#43;1][j]);//和当前的f[i&#43;1][j]比较d[i&#43;1][j&#43;1]&#61;max(d[i&#43;1][j&#43;1],d[i][j]&#43;a[i&#43;1][j&#43;1]);//和当前的f[i&#43;1][j&#43;1]比较}//答案可能是最后一行的任意一个&#xff0c;所以把最后一行搜索一遍&#xff0c;最大的赋给ansans&#61;0;for(int i&#61;0;i<n;i&#43;&#43;)ans&#61;max(ans,d[n-1][i]);cout<<ans<<endl;for(int i&#61;0;i<n;&#43;&#43;i){for(int j&#61;0;j<&#61;i;&#43;&#43;j){cout<<d[i][j]<<" ";}cout<<endl;}}return 0;
}

2、逆推

//数字金字塔-逆推
//d数组储存顺序&#xff1a;记录从顶端向底部走的路径最优值&#xff08;自顶向下&#xff09;
#include
#include
using namespace std;
int a[1005][1005];//储存数塔
int d[1005][1005];//从该点到底端的最大数字和
int main()
{int i,j,n,ans;while(cin>>n){memset(d,-1,sizeof(d));for(i&#61;0;i<n;i&#43;&#43;)for(j&#61;0;j<&#61;i;j&#43;&#43;){cin>>a[i][j];}//逆推&#xff08;自顶向下&#xff09;d[0][0]&#61;a[0][0];for(int i&#61;1;i<n;i&#43;&#43;){d[i][0]&#61;d[i-1][0]&#43;a[i][0];//最左的位置没有左上方d[i][i]&#61;d[i-1][i-1]&#43;a[i][i];//最右的位置没有右上方for(int j&#61;0;j<&#61;i;j&#43;&#43;)//在左上方和右上方取较大的d[i][j]&#61;max(d[i-1][j-1],d[i-1][j])&#43;a[i][j];}//答案可能是最后一行的任意一个&#xff0c;所以把最后一行搜索一遍&#xff0c;最大的赋给ansans&#61;0;for(int i&#61;0;i<n;i&#43;&#43;)ans&#61;max(ans,d[n-1][i]);cout<<ans<<endl;;for(int i&#61;0;i<n;&#43;&#43;i){for(int j&#61;0;j<&#61;i;&#43;&#43;j){cout<<d[i][j]<<" ";}cout<<endl;}}return 0;
}

附注&#xff1a;

动态规划相比较于深度搜索算法是一种高效算法。若能成功运用&#xff0c;必会使程序效率&#xff0c;无论时间还是空间&#xff0c;都有着质的飞跃。

掌握好动态规划&#xff0c;关键还是在于理解动态规划算法的基础上&#xff0c;多找一些相关的题进行练习。


推荐阅读
  • 本题要求在一组数中反复取出两个数相加,并将结果放回数组中,最终求出最小的总加法代价。这是一个经典的哈夫曼编码问题,利用贪心算法可以有效地解决。 ... [详细]
  • 本文介绍了如何使用暴力方法解决HDU5444问题。代码通过逐个检查输入数据,确保在所有情况下都能找到正确的解决方案。 ... [详细]
  • 在高并发需求的C++项目中,我们最初选择了JsonCpp进行JSON解析和序列化。然而,在处理大数据量时,JsonCpp频繁抛出异常,尤其是在多线程环境下问题更为突出。通过分析发现,旧版本的JsonCpp存在多线程安全性和性能瓶颈。经过评估,我们最终选择了RapidJSON作为替代方案,并实现了显著的性能提升。 ... [详细]
  • 本文介绍两道有趣的编程问题:一是寻找给定数字n的连续数字序列及其个数,二是模拟一个翻杯子的游戏。同时附带一道智商题供读者思考。 ... [详细]
  • 深入解析Spring启动过程
    本文详细介绍了Spring框架的启动流程,帮助开发者理解其内部机制。通过具体示例和代码片段,解释了Bean定义、工厂类、读取器以及条件评估等关键概念,使读者能够更全面地掌握Spring的初始化过程。 ... [详细]
  • 由二叉树到贪心算法
    二叉树很重要树是数据结构中的重中之重,尤其以各类二叉树为学习的难点。单就面试而言,在 ... [详细]
  • 探讨ChatGPT在法律和版权方面的潜在风险及影响,分析其作为内容创造工具的合法性和合规性。 ... [详细]
  • 本文探讨了符号三角形问题,该问题涉及由相同数量的“+”和“-”符号组成的三角形。通过递归回溯法,可以有效地搜索并计算符合条件的符号三角形的数量。 ... [详细]
  • 并发编程 12—— 任务取消与关闭 之 shutdownNow 的局限性
    Java并发编程实践目录并发编程01——ThreadLocal并发编程02——ConcurrentHashMap并发编程03——阻塞队列和生产者-消费者模式并发编程04——闭锁Co ... [详细]
  • JavaScript中的数组是数据集合的核心结构之一,内置了多种实用的方法。掌握这些方法不仅能提高开发效率,还能显著提升代码的质量和可读性。本文将详细介绍数组的创建方式及常见操作方法。 ... [详细]
  • 二叉树的链表实现
    本文介绍了一种使用链表结构表示二叉树的方法。通过定义节点结构和相关操作函数,可以方便地创建、插入和遍历二叉树。 ... [详细]
  • 在编译BSP包过程中,遇到了一个与 'gets' 函数相关的编译错误。该问题通常发生在较新的编译环境中,由于 'gets' 函数已被弃用并视为安全漏洞。本文将详细介绍如何通过修改源代码和配置文件来解决这一问题。 ... [详细]
  • 本文介绍了一种基于选择排序思想的高效排序方法——堆排序。通过使用堆数据结构,堆排序能够在每次查找最大元素时显著提高效率。文章详细描述了堆排序的工作原理,并提供了完整的C语言代码实现。 ... [详细]
  • 深入解析 Android IPC 中的 Messenger 机制
    本文详细介绍了 Android 中基于消息传递的进程间通信(IPC)机制——Messenger。通过实例和源码分析,帮助开发者更好地理解和使用这一高效的通信工具。 ... [详细]
  • 本文探讨了如何在Classic ASP中实现与PHP的hash_hmac('SHA256', $message, pack('H*', $secret))函数等效的哈希生成方法。通过分析不同实现方式及其产生的差异,提供了一种使用Microsoft .NET Framework的解决方案。 ... [详细]
author-avatar
xz7777
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有