动态规划的特点及其应用[安徽 张辰]

算法学习——动态规划 总结 一、动态规划特点。 求解目标具有最优解。 目标问题的最优解可以分解为各个子问题的最优解。 大问题分解为若干小问题后,这些小问题之间还有相互重叠的更小的子问题。 从上往下分析问题,从下往上求解问题。 PS:总结于 《剑指offer》author:何海涛 二、动态规划条件。 优化子结构。即一个问题的优化解包含子问题的优化解。作用:可以缩小子问题集合;保证了可以从下而上求解问题呢 ... 阅读详情
动态规划的特点及其应用安徽 张辰   目 录 (点击进入) 【关键词】【摘要】【正文】 §1动态规划的本质 §1.1多阶段决策问题 §1.2阶段与状态 §1.3决策和策略 §1.4最优化原理与无后效性 §1.5最优指标函数和规划方程 §2动态规划的设计与实现 §2.1动态规划的多样性 §2.2动态规划的模式性 §2.3动态规划的技巧性 §3动态规划与一些算法的比较 §3.1动态规划与递推 §3.2动态规划与搜索 §3.3动态规划与网络流 §4结语【附录:部分试题与源程序】 1.“花店橱窗布置问题”试题 2.“钉子与小球”试题 3.例2“花店橱窗布置问题”方法1的源程序 4.例2“花店橱窗布置问题”方法2的源程序 5.例3“街道问题”的扩展 6.例4“mod 4最优路径问题”的源程序 7.例5“钉子与小球”的源程序 8.例6的源程序,“N个人的街道问题” 【参考文献】【关键词】动态规划 阶段【摘要】动态规划是信息学竞赛中的常见算法,本文的主要内容就是分析它的特点。文章的第一部分首先探究了动态规划的本质,因为动态规划的特点是由它的本质所决定的。第二部分从动态规划的设计和实现这两个角度分析了动态规划的多样性、模式性、技巧性这三个特点。第三部分将动态规划和递推、搜索、网络流这三个相关算法作了比较,从中探寻动态规划的一些更深层次的特点。文章在分析动态规划的特点的同时,还根据这些特点分析了我们在解题中应该怎样利用这些特点,怎样运用动态规划。这对我们的解题实践有一定的指导意义。【正文】动态规划是编程解题的一种重要的手段,在如今的信息学竞赛中被应用得越来越普遍。最近几年的信息学竞赛,不分大小,几乎每次都要考察到这方面的内容。因此,如何更深入地了解动态规划,从而更为有效地运用这个解题的有力武器,是一个值得深入研究的问题。要掌握动态规划的应用技巧,就要了解它的各方面的特点。首要的,是要深入洞悉动态规划的本质。 §1动态规划的本质动态规划是在本世纪50年代初,为了解决一类多阶段决策问题而诞生的。那么,什么样的问题被称作多阶段决策问题呢? §1.1多阶段决策问题说到多阶段决策问题,人们很容易举出下面这个例子。 [例1]               7                          4   3           8           6                          7    5            4               6                5             6   A1 B1 B2 C1 C2 C3 D1多段图中的最短路径问题:在下图中找出从A1到D1的最短路径。仔细观察这个图不难发现,它有一个特点。我们将图中的点分为四类(图中的A、B、C、D),那么图中所有的边都处于相邻的两类点之间,并且都从前一类点指向后一类点。这样,图中的边就被分成了三类(AàB、BàC、CàD)。我们需要从每一类中选出一条边来,组成从A1到D1的一条路径,并且这条路径是所有这样的路径中的最短者。从上面的这个例子中,我们可以大概地了解到什么是多阶段决策问题。更精确的定义如下:多阶段决策过程,是指这样的一类特殊的活动过程,问题可以按时间顺序分解成若干相互联系的阶段,在每一个阶段都要做出决策,全部过程的决策是一个决策序列[1]。要使整个活动的总体效果达到最优的问题,称为多阶段决策问题。从上述的定义中,我们可以明显地看出,这类问题有两个要素。一个是阶段,一个是决策。 §1.2阶段与状态阶段:将所给问题的过程,按时间或空间特征分解成若干相互联系的阶段,以便按次序去求每阶段的解。常用字母k表示阶段变量。[1] 阶段是问题的属性。多阶段决策问题中通常存在着若干个阶段,如上面的例子,就有A、B、C、D这四个阶段。在一般情况下,阶段是和时间有关的;但是在很多问题(我的感觉,特别是信息学问题)中,阶段和时间是无关的。从阶段的定义中,可以看出阶段的两个特点,一是“相互联系”,二是“次序”。阶段之间是怎样相互联系的?就是通过状态和状态转移。状态:各阶段开始时的客观条件叫做状态。描述各阶段状态的变量称为状态变量,常用sk表示第k阶段的状态变量,状态变量sk的取值集合称为状态集合,用Sk表示。[1] 状态是阶段的属性。每个阶段通常包含若干个状态,用以描述问题发展到这个阶段时所处在的一种客观情况。在上面的例子中,行人从出发点A1走过两个阶段之后,可能出现的情况有三种,即处于C1、C2或C3点。那么第三个阶段就有三个状态S3={C1,C2,C3}。每个阶段的状态都是由以前阶段的状态以某种方式“变化”而来,这种“变化”称为状态转移(暂不定义)。上例中C3点可以从B1点过来,也可以从B2点过来,从阶段2的B1或B2状态走到阶段3的C3状态就是状态转移。状态转移是导出状态的途径,也是联系各阶段的途径。说到这里,可以提出应用动态规划的一个重要条件。那就是将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的发展,而只能通过当前的这个状态。换句话说,每个状态都是“过去历史的一个完整总结[1]”。这就是无后效性。对这个性质,下文还将会有解释。 §1.3决策和策略上面的阶段与状态只是多阶段决策问题的一个方面的要素,下面是另一个方面的要素——决策。决策:当各段的状态取定以后,就可以做出不同的决定,从而确定下一阶段的状态,这种决定称为决策。表示决策的变量,称为决策变量,常用uk(sk)表示第k阶段当状态为sk时的决策变量。在实际问题中,决策变量的取值往往限制在一定范围内,我们称此范围为允许决策集合。常用Dk(sk)表示第k阶段从状态sk出发的允许决策集合。显然有uk(sk) ÎDk(sk)。[1] 决策是问题的解的属性。决策的目的就是“确定下一阶段的状态”,还是回到上例,从阶段2的B1状态出发有三条路,也就是三个决策,分别导向阶段3的C1、C2、C3三个状态,即D2(B1)={C1,C2,C3}。有了决策,我们可以定义状态转移:动态规划中本阶段的状态往往是上一阶段和上一阶段的决策结果,由第k段的状态sk和本阶段的决策uk确定第k+1段的状态sk+1的过程叫状态转移。状态转移规律的形式化表示sk+1=Tk(sk,uk)称为状态转移方程。这样看来,似乎决策和状态转移有着某种联系。我的理解,状态转移是决策的目的,决策是状态转移的途径。各段决策确定后,整个问题的决策序列就构成一个策略,用p1,n={u1(s1),u2(s2),…, un(sn)}表示。对每个实际问题,可供选择的策略有一定范围,称为允许策略集合,记作P1,n,使整个问题达到最有效果的策略就是最优策略。[1] 说到这里,又可以提出运用动态规划的一个前提。即这个过程的最优策略应具有这样的性质:无论初始状态及初始决策如何,对于先前决策所形成的状态而言,其以后的所有决策应构成最优策略[1]。这就是最优化原理。简言之,就是“最优策略的子策略也是最优策略”。 §1.4最优化原理与无后效性这里,我把最优化原理定位在“运用动态规划的前提”。这是因为,是否符合最优化原理是一个问题的本质特征。对于不满足最优化原理的一个多阶段决策问题,整体上的最优策略p1,n同任何一个阶段k上的决策uk或任何一组阶段k1…k2上的子策略pk1,k2都不存在任何关系。如果要对这样的问题动态规划的话,我们从一开始所作的划分阶段等努力都将是徒劳的。而我把无后效性定位在“应用动态规划的条件”,是因为动态规划是按次序去求每阶段的解,如果一个问题有后效性,那么这样的次序便是不合理的。但是,我们可以通过重新划分阶段,重新选定状态,或者增加状态变量的个数等手段,来是问题满足无后效性这个条件。说到底,还是要确定一个“序”。在信息学的多阶段决策问题中,绝大部分都是能够满足最优化原理的,但它们往往会在后效性这一点上来设置障碍。所以在解题过程中,我们会特别关心“序”。对于有序的问题,就会考虑到动态规划;对于无序的问题,也会想方设法来使其有序。 §1.5最优指标函数和规划方程最优指标函数:用于衡量所选定策略优劣的数量指标称为指标函数,最优指标函数记为fk(sk),它表示从第k段状态sk采用最优策略p*k,n到过程终止时的最佳效益值[1]。最优指标函数其实就是我们真正关心的问题的解。在上面的例子中,f2(B1)就表示从B1点到终点D1点的最短路径长度。我们求解的最终目标就是f1(A1)。最优指标函数的求法一般是一个从目标状态出发的递推公式,称为规划方程: 其中sk是第k段的某个状态,uk是从sk出发的允许决策集合Dk(sk)中的一个决策,Tk(sk,uk)是由sk和uk所导出的第k+1段的某个状态sk+1,g(x,uk)是定义在数值x和决策uk上的一个函数,而函数opt表示最优化,根据具体问题分别表为max或min。,称为边界条件。上例中的规划方程就是: 边界条件为 这里是一种从目标状态往回推的逆序求法,适用于目标状态确定的问题。在我们的信息学问题中,也有很多有着确定的初始状态。当然,对于初始状态确定的问题,我们也可以采用从初始状态出发往前推的顺序求法。事实上,这种方法对我们来说要更为直观、更易设计一些,从而更多地出现在我们的解题过程中。我们本节所讨论的这些理论虽然不是本文的主旨,但是却对下面要说的动态规划的特点起着基础性的作用。 §2动态规划的设计与实现上面我们讨论了动态规划的一些理论,本节我们将通过几个例子中,动态规划的设计与实现,来了解动态规划的一些特点。 §2.1动态规划的多样性 [例2]  花店橱窗布置问题(IOI99)试题见附录本题虽然是本届IOI中较为简单的一题,但其中大有文章可作。说它简单,是因为它有序,因此我们一眼便可看出这题应该用动态规划来解决。但是,如何动态规划呢?如何划分阶段,又如何选择状态呢? <方法1>以花束的数目来划分阶段。在这里,阶段变量k表示的就是要布置的花束数目(前k束花),状态变量sk表示第k束花所在的花瓶。而对于每一个状态sk,决策就是第k-1束花应该放在哪个花瓶,用uk表示。最优指标函数fk(sk)表示前k束花,其中第k束插在第sk个花瓶中,所能取得的最大美学值。状态转移方程为       规划方程为                      (其中A(i,j)是花束i插在花瓶j中的美学值)边界条件 (V是花瓶总数,事实上这是一个虚拟的边界) <方法2>以花瓶的数目来划分阶段。在这里阶段变量k表示的是要占用的花瓶数目(前k个花瓶),状态变量sk表示前k个花瓶中放了多少花。而对于任意一个状态sk,决策就是第sk束花是否放在第k个花瓶中,用变量uk=1或0来表示。最优指标函数fk(sk)表示前k个花瓶中插了sk束花,所能取得的最大美学值。状态转移方程为 规划方程为 边界条件为 两种划分阶段的方法,引出了两种状态表示法,两种规划方式,但是却都成功地解决了问题。只不过因为决策的选择有多有少,所以算法的时间复杂度也就不同。[2] 这个例子具有很大的普遍性。有很多的多阶段决策问题都有着不止一种的阶段划分方法,因而往往就有不止一种的规划方法。有时各种方法所产生的效果是差不多的,但更多的时候,就像我们的例子一样,两种方法会在某个方面有些区别。所以,在用动态规划解题的时候,可以多想一想是否有其它的解法。对于不同的解法,要注意比较,好的算法好在哪里,差一点的算法差在哪里。从各种不同算法的比较中,我们可以更深刻地领会动态规划的构思技巧。 §2.2动态规划的模式性这个可能做过动态规划的人都有体会,从我们上面对动态规划的分析也可以看出来。动态规划的设计都有着一定的模式,一般要经历以下几个步骤。划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。注意这若干个阶段一定要是有序的或者是可排序的,否则问题就无法求解。选择状态:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性。确定决策并写出状态转移方程:之所以把这两步放在一起,是因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以,如果我们确定了决策,状态转移方程也就写出来了。但事实上,我们常常是反过来做,根据相邻两段的各状态之间的关系来确定决策。写出规划方程(包括边界条件):在第一部分中,我们已经给出了规划方程的通用形式化表达式。一般说来,只要阶段、状态、决策和状态转移确定了,这一步还是比较简单的。动态规划的主要难点在于理论上的设计,一旦设计完成,实现部分就会非常简单。大体上的框架如下: 对f1(s1)初始化(边界条件) for kß2 to n(这里以顺序求解为例)   对每一个skÎSk   fk(sk)ß一个极值(∞或-∞) 对每一个uk(sk)ÎDk(sk)   sk-1ßTk(sk,uk) tßg(fk-1(sk-1),uk) y t比fk(sk)更优 n fk(sk)ßt   输出fn(sn) 这个N-S图虽然不能代表全部,但足可以概括大多数。少数的一些特殊的动态规划,其实现的原理也是类似,可以类比出来。我们到现在对动态规划的分析,主要是在理论上、设计上,原因也就在此。掌握了动态规划的模式性,我们在用动态规划解题时就可以把主要的精力放在理论上的设计。一旦设计成熟,问题也就基本上解决了。而且在设计算法时也可以按部就班地来。但是“物极必反”,太过拘泥于模式就会限制我们的思维,扼杀优良算法思想的产生。我们在解题时,不妨发挥一下创造性,去突破动态规划的实现模式,这样往往会收到意想不到的效果。[3] §2.3动态规划的技巧性上面我们所说的动态规划的模式性,主要指的是实现方面。而在设计方面,虽然它较为严格的步骤性,但是它的设计思想却是没有一定的规律可循的。这就需要我们不断地在实践当中去掌握动态规划的技巧,下面仅就一个例子谈一点我自己的体会。 [例3]     3    7    4    8 7    6    3    5    3    4    6    3    5 2    8    5    9    4    3    6    3    5 8    7    4    3    7    5    4    6    2          街道问题:在下图中找出从左下角到右上角的最短路径,每步只能向右方或上方走。这是一道简单而又典型的动态规划题,许多介绍动态规划的书与文章中都拿它来做例子。通常,书上的解答是这样的:     D1   E1   F1   G1   H1   C1   D2   E2   F2   G2   B1   C2   D3   E3   F3   A1   B2   C3   D4   E4按照图中的虚线来划分阶段,即阶段变量k表示走过的步数,而状态变量sk表示当前处于这一阶段上的哪一点(各点所对应的阶段和状态已经用ks在地图上标明)。这时的模型实际上已经转化成了一个特殊的多段图。用决策变量uk=0表示向右走,uk=1表示向上走,则状态转移方程如下: (这里的row是地图竖直方向的行数) D1   D2   D3   D4   D5   C1   C2   C3   C4   C5   B1   B2   B3   B4   B5   A1   A2   A3   A4   A5我们看到,这个状态转移方程需要根据k的取值分两种情况讨论,显得非常麻烦。相应的,把它代入规划方程而付诸实现时,算法也很繁。因而我们在实现时,一般是不会这么做的,而代之以下面方法:将地图中的点规则地编号如上,得到的规划方程如下: (这里Distance表示相邻两点间的边长)这样做确实要比上面的方法简单多了,但是它已经破坏了动态规划的本来面目,而不存在明确的阶段特征了。如果说这种方法是以地图中的行(A、B、C、D)来划分阶段的话,那么它的“状态转移”就不全是在两个阶段之间进行的了。也许这没什么大不了的,因为实践比理论更有说服力。但是,如果我们把题目扩展一下:在地图中找出从左下角到右上角的两条路径,两条路径中的任何一条边都不能重叠,并且要求两条路径的总长度最短。这时,再用这种“简单”的方法就不太好办了。如果非得套用这种方法的话,则最优指标函数就需要有四维的下标,并且难以处理两条路径“不能重叠”的问题。而我们回到原先“标准”的动态规划法,就会发现这个问题很好解决,只需要加一维状态变量就成了。即用sk=(ak,bk)分别表示两条路径走到阶段k时所处的位置,相应的,决策变量也增加一维,用uk=(xk,yk)分别表示两条路径的行走方向。状态转移时将两条路径分别考虑: 在写规划方程时,只要对两条路径走到同一个点的情况稍微处理一下,减少可选的决策个数: 从这个例子中可以总结出设计动态规划算法的一个技巧:状态转移一般是在相邻的两个阶段之间(有时也可以在不相邻的两个阶段间),但是尽量不要在同一个阶段内进行。动态规划是一种很灵活的解题方法,在动态规划算法的设计中,类似的技巧还有很多。要掌握动态规划的技巧,有两条途径:一是要深刻理解动态规划的本质,这也是我们为什么一开始就探讨它的本质的原因;二是要多实践,不但要多解题,还要学会从解题中探寻规律,总结技巧。 §3动态规划与一些算法的比较动态规划作为诸多解题方法中的一种,必然和其他一些算法有着诸多联系。从这些联系中,我们也可以看出动态规划的一些特点。 §3.1动态规划与递推 ——动态规划是最优化算法由于动态规划的“名气”如此之大,以至于很多人甚至一些资料书上都往往把一种与动态规划十分相似的算法,当作是动态规划。这种算法就是递推。实际上,这两种算法还是很容易区分的。按解题的目标来分,信息学试题主要分四类:判定性问题、构造性问题、计数问题和最优化问题。我们在竞赛中碰到的大多是最优化问题,而动态规划正是解决最优化问题的有力武器,因此动态规划在竞赛中的地位日益提高。而递推法在处理判定性问题和计数问题方面也是一把利器。下面分别就两个例子,谈一下递推法和动态规划在这两个方面的联系。 [例4]  mod 4 最优路径问题:在下图中找出从第1点到第4点的一条路径,要求路径长度mod 4的余数最小。      3         1          0     1         2          2    0         2          3 1 2 3 4这个图是一个多段图,而且是一个特殊的多段图。虽然这个图的形式比一般的多段图要简单,但是这个最优路径问题却不能用动态规划来做。因为一条从第1点到第4点的最优路径,在它走到第2点、第3点时,路径长度mod 4的余数不一定是最小,也就是说最优策略的子策略不一定最优——这个问题不满足最优化原理。但是我们可以把它转换成判定性问题,用递推法来解决。判断从第1点到第k点的长度mod 4为sk的路径是否存在,用fk(sk)来表示,则递推公式如下:(边界条件) (这里lenk,i表示从第k-1点到第k点之间的第i条边的长度,方括号表示“或(or)”运算)最后的结果就是可以使f4(s4)值为真的最小的s4值。这个递推法的递推公式和动态规划的规划方程非常相似,我们在这里借用了动态规划的符号也就是为了更清楚地显示这一点。其实它们的思想也是非常相像的,可以说是递推法借用了动态规划的思想解决了动态规划不能解决的问题。有的多阶段决策问题(像这一题的阶段特征就很明显),由于不能满足最优化原理等使用动态规划的先决条件,而无法应用动态规划。在这时可以将最优指标函数的值当作“状态”放到下标中去,从而变最优化问题为判定性问题,再借用动态规划的思想,用递推法来解决问题。 [例5]  钉子与小球(NOI99)试题见附录这个题目一看就不觉让人想起一道经典的动态规划题。下面先让我们回顾一下这个问题。数字三角形(IOI94)在下图中求从顶至低某处的一条路径,使该路径所经过的数字的总和最大,每一步只能向左下或右下走。 7 3  8 8  1  0 2  7  4  4 4  5  2  6  5 在这个问题中,我们按走过的行数来划分阶段,以走到每一行时所在的位置来作为状态,决策就是向左下走(用0表示)或向右下走(用1表示)。状态转移方程: 规划方程: 边界条件: 这是一个比较简单的最优化问题,我们还可以把这个问题改成一个更加简单的整数统计问题:求顶点到每一点的路径总数。把这个总数用fk(sk)表示,那么递推公式就是: 在这里,虽然求和公式只有两项,但我们仍然用∑的形式表示,就是为了突出这个递推公式和上面的规划方程的相似之处。这两个公式的边界条件都是一模一样的。再回到我们上面的“钉子与小球”问题,这是一个概率统计问题。我们继续沿用上面的思想,用fk(sk)表示小球落到第k行第sk个钉子上的概率,则递推公式如下: (这里函数Existk(sk)表示第k行第sk个钉子是否存在,存在则取1,不存在则取0)边界条件 可以看出这个公式较之上面的两个式子虽然略有变化,但是其基本思想还是类似的。在解这个问题的过程中,我们再次运用了动态规划的思想。一般说来,很多最优化问题都有着对应的计数问题;反过来,很多计数问题也有着对应的最优化问题。因此,我们在遇到这两类问题时,不妨多联系、多发展,举一反三,从比较中更深入地理解动态规划的思想。其实递推和动态规划这两种方法的思想本来就很相似,也不必说是谁借用了谁的思想。关键在于我们要掌握这种思想,这样我们无论在用动态规划法解最优化问题,或是在用递推法解判定型、计数问题时,都能得心应手、游刃有余了。 §3.2动态规划与搜索 ——动态规划是高效率、高消费算法同样是解决最优化问题,有的题目我们采用动态规划,而有的题目我们则需要用搜索。这其中有没有什么规则呢?我们知道,撇开时空效率的因素不谈,在解决最优化问题的算法中,搜索可以说是“万能”的。所以动态规划可以解决的问题,搜索也一定可以解决。把一个动态规划算法改写成搜索是非常方便的,状态转移方程、规划方程以及边界条件都可以直接“移植”,所不同的只是求解顺序。动态规划是自底向上的递推求解,而搜索则是自顶向下的递归求解(这里指深度搜索,宽度搜索类似)。反过来,我们也可以把搜索算法改写成动态规划。状态空间搜索实际上是对隐式图中的点进行枚举,这种枚举是自顶向下的。如果把枚举的顺序反过来,变成自底向上,那么就成了动态规划。(当然这里有个条件,即隐式图中的点是可排序的,详见下一节。) A1 B1 B2 C1 C2 C3 A1 B1 B2 C1 C2 C3 C2 A1 B1 B2 C1 C2 C3 (a)                     (b)                     (c)正因为动态规划和搜索有着求解顺序上的不同,这也造成了它们时间效率上的差别。在搜索中,往往会出现下面的情况:对于上图(a)这样几个状态构成的一个隐式图,用搜索算法就会出现重复,如上图(b)所示,状态C2被搜索了两次。在深度搜索中,这样的重复会引起以C2为根整个的整个子搜索树的重复搜索;在宽度搜索中,虽然这样的重复可以立即被排除,但是其时间代价也是不小的。而动态规划就没有这个问题,如上图(c)所示。一般说来,动态规划算法在时间效率上的优势是搜索无法比拟的。(当然对于某些题目,根本不会出现状态的重复,这样搜索和动态规划的速度就没有差别了。)而从理论上讲,任何拓扑有序(现实中这个条件常常可以满足)的隐式图中的搜索算法都可以改写成动态规划。但事实上,在很多情况下我们仍然不得不采用搜索算法。那么,动态规划算法在实现上还有什么障碍吗? A1 B1 B2 C1 C2 C3 A1 B1 B2 C2 C2 A1 B1 B2 C1 C2 C3 (a)                     (b)                     (c)考虑上图(a)所示的隐式图,其中存在两个从初始状态无法达到的状态。在搜索算法中,这样的两个状态就不被考虑了,如上图(b)所示。但是动态规划由于是自底向上求解,所以就无法估计到这一点,因而遍历了全部的状态,如上图(c)所示。一般说来,动态规划总要遍历所有的状态,而搜索可以排除一些无效状态。更重要的事搜索还可以剪枝,可能剪去大量不必要的状态,因此在空间开销上往往比动态规划要低很多。如何协调好动态规划的高效率与高消费之间的矛盾呢?有一种折衷的办法就是记忆化算法。记忆化算法在求解的时候还是按着自顶向下的顺序,但是每求解一个状态,就将它的解保存下来,以后再次遇到这个状态的时候,就不必重新求解了。这种方法综合了搜索和动态规划两方面的优点,因而还是很有实用价值的。 §3.3动态规划与网络流 ——动态规划是易设计易实现算法由于图的关系复杂而无序,一般难以呈现阶段特征(除了特殊的图如多段图,或特殊的分段方法如Floyd),因此动态规划在图论中的应用不多。但有一类图,它的点却是有序的,这就是有向无环图。在有向无环图中,我们可以对点进行拓扑排序,使其体现出有序的特征,从而据此划分阶段。在有向无还图中求最短路径的算法[4],已经体现出了简单的动态规划思想。但动态规划在图论中还有更有价值的应用。下面先看一个例子。 [例6]  N个人的街道问题:在街道问题(参见例3)中,若有N个人要从左下角走向右上角,要求他们走过的边的总长度最大。当然,这里每个人也只能向右或向上走。下面是一个样例,左图是从出发地到目的地的三条路径,右图是他们所走过的边,这些边的总长度为5 + 4 + 3 + 6 + 3 + 3 + 5 + 8 + 8 + 7 + 4 + 5 + 9 + 5 + 3 = 78(不一定是最大)。    3    7    4    8 7    6    3    5    3    4    6    3    5 2    8    5    9    4    3    6    3    5 8    7    4    3    7    5    4    6    2              3    7    4    8 7    6    3    5    3    4    6    3    5 2    8    5    9    4    3    6    3    5 8    7    4    3    7    5    4    6    2          这个题目是对街道问题的又一次扩展。仿照街道问题的解题方法,我们仍然可以用动态规划来解决本题。不过这一次是N个人同时走,状态变量也就需要用N维来表示,。相应的,决策变量也要变成N维,uk=(uk,1,uk,2,…,uk,N)。状态转移方程不需要做什么改动: 在写规划方程时,需要注意在第k阶段,N条路径所走过的边的总长度的计算,在这里我就用gk(sk,uk)来表示了: 边界条件为 可见将原来的动态规划算法移植到这个问题上来,在理论上还是完全可行的。但是,现在的这个动态规划算法的时空复杂度已经是关于N的指数函数,只要N稍微大一点,这个算法就不可能实现了。下面我们换一个思路,将N条路径看成是网络中一个流量为N的流,这样求解的目标就是使这个流的费用最大。但是本题又不同于一般的费用流问题,在每一条边e上的流费用并不是流量和边权的乘积 ,而是用下式计算: 为了使经典的费用流算法适用于本题,我们需要将模型稍微转化一下: c1=1 w1=w0     c2=∞ w2=0 w0如图,将每条边拆成两条。拆开后一条边上有权,但是容量限制为1;另一条边没有容量限制,但是流过这条边就不能计算费用了。这样我们就把问题转化成了一个标准的最大费用固定流问题。这个算法可以套用经典的最小费用最大流算法,在此就不细说了。(参见附录中的源程序)这个例题是我仿照IOI97的“障碍物探测器”一题[6]编出来的。“障碍物探测器”比这一题要复杂一些,但是基本思想是相似的。类似的题目还有99年冬令营的“迷宫改造”[7]。从这些题目中都可以看到动态规划和网络流的联系。推广到一般情况,任何有向无环图中的费用流问题在理论上说,都可以用动态规划来解决。对于流量为N(如果流量不固定,这个N需要事先求出来)的费用流问题,用N维的变量sk=(sk,1,sk,2,…,sk,N)来描述状态,其中sk,iÎV(1£i£N)。相应的,决策也用N维的变量uk=(uk,1,uk,2,…,uk,N)来表示,其中uk,iÎE(sk,i)(1£i£N),E(v)表示指向v的弧集。则状态转移方程可以这样表示: sk-1,i = uk,i的弧尾结点规划方程为 边界条件为 但是,由于动态规划算法是指数级算法,因而在实现中的局限性很大,仅可用于一些N非常小的题目。然而在竞赛解题中,比如上面说到的IOI97以及99冬令营测试时,我们使用动态规划的倾向性很明显(“障碍物探测器”中,我们用的是贪心策略,求N=1或N=2时的局部最优解[8])。这主要有两个原因:一.虽然网络流有着经典的算法,但是在竞赛中不可能出现经典的问题。如果要运用网络流算法,则需要经过一番模型转化,有时这个转化还是相当困难的。因此在算法的设计上,灵活巧妙的动态规划算法反而要更为简单一些。二.网络流算法实现起来很繁,这是被人们公认的。因而在竞赛的紧张环境中,实现起来有一定模式的动态规划算法又多了一层优势。正由于动态规划算法在设计和实现上的简便性,所以在N不太大时,也就是在动态规划可行的情况下,我们还是应该尽量运用动态规划。 §4结语本文的内容比较杂,是我几年来对动态规划的参悟理解、心得体会。虽然主要的篇幅讲的都是理论,但是根本的目的还是指导实践。动态规划,据我认为,是当今信息学竞赛中最灵活、也最能体现解题者水平的一类解题方法。本文内容虽多,不能涵盖动态规划之万一。“纸上得来终觉浅,绝知此事要躬行。”要想真正领悟、理解动态规划的思想,掌握动态规划的解题技巧,还需要在实践中不断地挖掘、探索。实践得多了,也就能体会到渐入佳境之妙了。动态规划,算法之常,运用之妙,存乎一心。【附录:部分试题与源程序】 1.      “花店橱窗布置问题”试题 LITTLE SHOP OF FLOWERS   PROBLEM You want to arrange the window of your flower shop in a most pleasant way. You have F bunches of flowers, each being of a different kind, and at least as many vases ordered in a row. The vases are glued onto the shelf and are numbered consecutively 1 through V, where V is the number of vases, from left to right so that the vase 1 is the leftmost, and the vase V is the rightmost vase. The bunches are moveable and are uniquely identified by integers between 1 and F. These id-numbers have a significance: They determine the required order of appearance of the flower bunches in the row of vases so that the bunch i must be in a vase to the left of the vase containing bunch j whenever i < j. Suppose, for example, you have bunch of azaleas (id-number=1), a bunch of begonias (id-number=2) and a bunch of carnations (id-number=3). Now, all the bunches must be put into the vases keeping their id-numbers in order. The bunch of azaleas must be in a vase to the left of begonias, and the bunch of begonias must be in a vase to the left of carnations. If there are more vases than bunches of flowers then the excess will be left empty. A vase can hold only one bunch of flowers.   Each vase has a distinct characteristic (just like flowers do). Hence, putting a bunch of flowers in a vase results in a certain aesthetic value, expressed by an integer. The aesthetic values are presented in a table as shown below. Leaving a vase empty has an aesthetic value of 0. 一点花商店   问题您想要安排您的花店窗口用一个最宜人的方式。您有花Fbunches,其中每一是不同的种类和连续被定购的至少许多个花瓶。 花瓶被胶合在架子上和从左到右连贯地被编号1通过V,其中V是花瓶的数量,以便花瓶1是最左的,并且花瓶v是最右的花瓶。束是可移动的和由在1和F.之间的整数独特识别。 这些id数字有意义: 他们确定花束的出现必需的命令在花瓶行的,以便束我必须在花瓶在包含束j的花瓶左边,每当i < j.假设,例如,您有束杜娟花(id-number=1),一束秋海棠(id-number=2)和一束康乃馨(id-number=3)。 现在,必须放所有束入保留他们的id数字的花瓶在有条有理。束杜娟花必须在花瓶在秋海棠左边,并且束秋海棠必须在花瓶在康乃馨左边。 如果比花有更多花瓶那么剩余将空出。 花瓶只可能拿着一束花。   每个花瓶有一个分明特征(象花)。 因此,投入一束花在花瓶导致有些审美价值,表达由整数。 审美价值在桌里被提出如下所示。 空出花瓶有审美值为0。     VASES   1 2 3 4 5 Bunches 1 (azaleas杜鹃花) 7 23 -5 -24 16 2 (begonias秋海棠) 5 21 -4 10 23 3 (carnation康乃馨) -21 5 -4 -20 20 According to the table, azaleas, for example, would look great in vase 2, but they would look awful in vase 4.   To achieve the most pleasant effect you have to maximize the sum of aesthetic values for the arrangement while keeping the required ordering of the flowers. If more than one arrangement has the maximal sum value, any one of them will be acceptable. You have to produce exactly one arrangement. 根据桌,杜娟花,例如,将看起来伟大在花瓶2,但是他们将看起来可怕在花瓶4。   要达到最宜人的作用您必须最大化审美价值的总和安排的,当保留必需预定花时。如果超过一个安排有最大的总和价值,任何他们中的一个将是可接受的。 您必须正确地引起一个安排。 ASSUMPTIONS 1 <= F <= 100 where F is the number of the bunches of flowers. The bunches are numbered 1 through F. F <= V <= 100 where V is the number of vases. -50 <= Aij <= 50 where Aij is the aesthetic value obtained by putting the flower bunch i into the vase j.   INPUT The input is a text file named flower.inp. The first line contains two numbers: F, V. The following F lines: Each of these lines contains V integers, so that Aij is given as the jth number on the (i+1)st line of the input file.   OUTPUT The output must be a text file named flower.out consisting of two lines: The first line will contain the sum of aesthetic values for your arrangement. The second line must present the arrangement as a list of F numbers, so that the k’th number on this line identifies the vase in which the bunch k is put.   EXAMPLE flower.inp: 3 5 7 23 -5 -24 16 5 21 -4 10 23 -21 5 -4 -20 20   flower.out: 53 2 4 5   EVALUATION Your program will be allowed to run 2 seconds. No partial credit can be obtained for a test case.   2.      “钉子与小球”试题有一个三角形木板,竖直立放,上面钉着n(n+1)/2颗钉子,还有(n+1)个格子(当n=5时如图1)。每颗钉子和周围的钉子的距离都等于d,每个格子的宽度也都等于d,且除了最左端和最右端的格子外每个格子都正对着最下面一排钉子的间隙。让一个直径略小于d的小球中心正对着最上面的钉子在板上自由滚落,小球每碰到一个钉子都可能落向左边或右边(概率各1/2),且球的中心还会正对着下一颗将要碰上的钉子。例如图2就是小球一条可能的路径。我们知道小球落在第i个格子中的概率 ,其中i为格子的编号,从左至右依次为0,1,...,n。现在的问题是计算拔掉某些钉子后,小球落在编号为m的格子中的概率pm。假定最下面一排钉子不会被拔掉。例如图3是某些钉子被拔掉后小球一条可能的路径。图1                          图2                          图3 输入第1行为整数n(2<=n<=50)和m(0<=m<=n)。以下n行依次为木板上从上至下n行钉子的信息,每行中‘*’表示钉子还在,‘.’ 表示钉子被拔去,注意在这n行中空格符可能出现在任何位置。   输出仅一行,是一个既约分数(0写成0/1),为小球落在编号为m的格子中的概率pm。既约分数的定义:A/B是既约分数,当且仅当A、B为正整数且A和B没有大于1的公因子。   样例输入 5 2 * * . * * * * . * * * * * * *   样例输出 7/16   3.      例2“花店橱窗布置问题”方法1的源程序 program IOI99_LittleShopOfFlowers; var     F,V :byte; {花束和花瓶的数目}     A   :array [1..100,1..100] of shortint;     {A[i,j]花束i放在花瓶j中的美学值}     Best    :array [0..100,0..100] of integer;         {Best[i,j]前i束花放在前j个花瓶中的最优解}     Previous    :array [1..100,1..100] of byte;         {Previous[i,j]在Best[i,j]的最优解中,花束i-1的位置}   procedure ReadIn;   {读入} var     i,j :byte; begin     reset(input);     readln(F,V);     for i:=1 to F do     begin         for j:=1 to V do             read(A[i,j]);         readln;     end;     close(input); end;   procedure Work;     {规划主过程} var     i,j,t   :byte; begin     fillchar(Best[0],sizeof(Best[0]),0); {边界条件}     for i:=1 to F do         for j:=i to V+i-F do         begin             Best[i,j]:=low(Best[i,j]);             for t:=i-1 to j-1 do    {枚举花束i-1的位置}                 if Best[i-1,t]>Best[i,j] then                 begin   {更新当前最优解}                     Best[i,j]:=Best[i-1,t];                     Previous[i,j]:=t;                 end;             inc(Best[i,j],A[i,j]);         end; end;   procedure Print;    {打印最优解} var     i,j :byte;     Put :array [1..100] of byte; begin     i:=F;     for j:=F+1 to V do  {选择全局最优解}         if Best[F,j]>Best[F,i] then             i:=j;   {i最后一束花的位置}     rewrite(output);     writeln(Best[F,i]);     for j:=F downto 1 do     begin         Put[j]:=i;         i:=Previous[j,i];     end;     for i:=1 to F do         write(Put[i],' ');     writeln;     close(output); end;   begin     assign(input,'flower.inp');     assign(output,'flower.out');     ReadIn;     Work;     Print; end. 4.      例2“花店橱窗布置问题”方法2的源程序 program IOI99_LittleShopOfFlowers; var     F,V :byte;  {花束和花瓶的数目}     A   :array [1..100,1..100] of shortint; {A[i,j]花束i放在花瓶j中的美学值}     Best    :array [0..100,0..100] of integer;         {Best[i,j]前i束花放在前j个花瓶中的最优解}     Choice  :array [0..100,0..100] of boolean;         {Choice[i,j] 花束i是否放在花瓶j中}   procedure ReadIn;   {读入} var     i,j :byte; begin     reset(input);     readln(F,V);     for i:=1 to F do     begin         for j:=1 to V do             read(A[i,j]);         readln;     end;     close(input); end;   procedure Work; {规划主过程} var     i,j :byte; begin     fillchar(Best[0],sizeof(Best[0]),0); {边界条件}     for i:=1 to F do     begin         Best[i,i]:=Best[i-1,i-1]+A[i,i];    {唯一的选择}         Choice[i,i]:=true;         for j:=i+1 to V+i-F do         begin             Choice[i,j]:=Best[i-1,j-1]+A[i,j]>Best[i,j-1];             if Choice[i,j]             then Best[i,j]:=Best[i-1,j-1]+A[i,j]    {i放在j中}             else Best[i,j]:=Best[i,j-1];    {i不放在j中}         end;     end; end;   procedure Print;    {打印最优解} var     i,j :byte;     Put :array [1..100] of byte;    {Put[i]花束i所在的花瓶} begin     rewrite(output);     writeln(Best[F,V]);     i:=F;     for j:=V downto 1 do    {倒推求Put}         if Choice[i,j] then         begin             Put[i]:=j;             dec(i);         end;     for i:=1 to F do     begin         write(Put[i]);         if i<F then write(' ');     end;     writeln;     close(output); end;   begin     assign(input,'flower.inp');     assign(output,'flower.out');     ReadIn;     Work;     Print; end. 5.      例3“街道问题”的扩展,找两条不重叠的最短路径 program N_Street; const     MaxSize=90; {地图的最大尺寸} type     TPlanarArr=array [1..MaxSize,1..MaxSize] of integer; var     Row,Col :byte;      {地图的行数和列数}     Best,B1 :TPlanarArr;    {动态规划中本阶段和上阶段的最优指标函数}     Street  :array [0..1] of TPlanarArr;    {地图中横向和纵向的边}   procedure ReadIn;   {读入} var     i,j :byte; begin     reset(input);     readln(Row,Col);     for i:=1 to Row do     begin         for j:=1 to Col-1 do             read(Street[0,i,j]);         readln;     end;     for j:=1 to Col do     begin         for i:=1 to Row-1 do             read(Street[1,i,j]);         readln;     end;     close(input); end;   procedure Work; {规划过程,和文章中不同,这里是逆序求解} var     k,      {阶段}     s1,s2,  {状态(二维)}     u1,u2,  {决策(二维)}     t1,t2,  {由s和t导出的上阶段的状态(二维)}     m,      {本阶段的状态总数}     m1,m2   :byte;  {Row和Col当中较小的数和较大的数}     e1,e2   :integer;   {u1,u2对应的两条边长}     function GetET(s,u:byte; var e:integer; var t:byte):boolean;    {求e,t}     begin         GetET:=false;         if  (k>=Row) and (s=1) and (u=1) or             (k>=Col) and (s=m) and (u=0) then             exit;   {判断越界}         GetET:=true;         if k<Row    {根据k分两种情况}         then begin             e:=Street[u,k+1-s,s];             t:=s+1-u;         end         else begin             e:=Street[u,Row+1-s,k-Row+s];             t:=s-u;         end;     end; begin     if Row<Col     then begin         m1:=Row; m2:=Col;     end     else begin         m1:=Col; m2:=Row;     end;     Best[1,1]:=0;     for k:=Row+Col-2 downto 1 do    {逆序求解}     begin         if k<m1         then m:=k         else if k>m2             then m:=Row+Col-k             else m:=m1;         B1:=Best;         for s1:=1 to m do             for s2:=1 to m do             begin                 Best[s1,s2]:=$7000; {初始化为一个较大的数}                 for u1:=0 to 1 do                     if GetET(s1,u1,e1,t1) then                         for u2:=0 to 1 do                             if ((s1<>s2) or (u1<>u2)) and                                 GetET(s2,u2,e2,t2) and                                 (B1[t1,t2]+e1+e2<Best[s1,s2]) then                                 Best[s1,s2]:=B1[t1,t2]+e1+e2;   {更新最优解}             end;     end; end;   begin     assign(input,'input.txt');     assign(output,'output.txt');     ReadIn;     Work;     rewrite(output);     writeln(Best[1,1]);     close(output); end. 6.      例4“mod 4最优路径问题”的源程序 program BestPath_mod4; const     MaxN=100;   {点数上限}     MaxE=100;   {两点之间的边数上限} var     N   :byte;  {点数}     E   :array [1..MaxN,0..MaxE] of integer;         {E[i,0]第i点到第i+1点之间的边数}         {E[i,j]第i点到第i+1点之间的第j条边长}     Best    :array [1..MaxN,0..3] of boolean;         {Best[k,s]从第1点到第k点长度mod 4的值为s的路径是否存在}   procedure ReadIn;   {读入} var     i,j :byte; begin     reset(input);     readln(N);     for i:=1 to N-1 do     begin         read(E[i,0]);         for j:=1 to E[i,0] do             read(E[i,j]);         readln;     end;     close(input); end;   procedure Work; {主递推过程} var     k,s,u   :byte;  {阶段、状态、决策} begin     fillchar(Best,sizeof(Best),false);     Best[1,0]:=true;    {边界条件}     for k:=2 to N do         for s:=0 to 3 do             for u:=1 to E[k-1,0] do                 Best[k,s]:=Best[k,s] or Best[k-1,($10000+s-E[k-1,u]) mod 4];                     {加上10000(h)为防止出现负数} end;   procedure Print;    {这里只输出最优路径mod 4的值,路径就不输出了} var     i   :byte; begin     for i:=0 to 3 do         if Best[N,i] then             break;     rewrite(output);     writeln(i);     close(output); end;   begin     assign(input,'input.txt');     assign(output,'output.txt');     ReadIn;     Work;     Print; end. 7.      例5“钉子与小球”的源程序 {$A+,B-,D+,E+,F-,G+,I+,L+,N+,O-,P-,Q-,R-,S-,T-,V+,X+,Y+} {$M 65520,0,655360} program Ball; var     N,M :byte;     Nail    :array [1..50,1..50] of byte;     Poss    :array [0..50,0..50] of comp;   procedure ReadIn; var     i,j :byte;     c   :char; begin     reset(input);     readln(N,M);     for i:=1 to N do         for j:=1 to i do         begin             repeat                 read(c);             until c in ['*','.'];             if c='*'             then Nail[i,j]:=1             else Nail[i,j]:=0;         end;     close(input); end;   procedure Work; var     i,j :byte; begin     fillchar(Poss,sizeof(Poss),0);     Poss[0,0]:=1;     for i:=1 to N do         Poss[0,0]:=Poss[0,0]*2;     for i:=0 to N-1 do         for j:=0 to i do             if Nail[i+1,j+1]=0             then Poss[i+2,j+1]:=Poss[i+2,j+1]+Poss[i,j]             else begin                 Poss[i+1,j]:=Poss[i+1,j]+Poss[i,j]/2;                 Poss[i+1,j+1]:=Poss[i+1,j+1]+Poss[i,j]/2;             end; end;   procedure Print; var     a,b :comp; begin;     rewrite(output);     a:=Poss[N,M];     if a=0     then writeln('0/1')     else begin         b:=a/2;         while b*2=a do         begin             a:=b; b:=a/2;             Poss[0,0]:=Poss[0,0]/2;         end;         writeln(a:0:0,'/',Poss[0,0]:0:0);     end;     close(output); end;   begin     assign(input,'input.txt');     assign(output,'output.txt');     ReadIn;     Work;     Print; end. 8.      例6的源程序,“N个人的街道问题”,网络流算法 {$A+,B-,D+,E+,F-,G+,I+,L+,N+,O-,P-,Q+,R+,S+,T-,V-,X+,Y+} {$M 65520,0,655360} program N_Street; const     MaxSize=100;    {地图的最大尺寸} var     Row,Col,        {地图的行数和列数}     N   :byte;  {人数N}     Horiz,  {横向的边}     Vert    :   {纵向的边}         array [1..MaxSize,1..MaxSize] of word;     F   :array [1..MaxSize,1..MaxSize,0..1] of byte;             {F[i,j,d]从点(i,j)向方向d去的流量}     Result  :word;  {最优结果}   procedure ReadIn;   {读入} var     i,j :byte; begin     reset(input);     readln(Row,Col,N);     for i:=1 to Row do     begin         for j:=1 to Col-1 do             read(Horiz[i,j]);         readln;     end;     for j:=1 to Col do     begin         for i:=1 to Row-1 do             read(Vert[i,j]);         readln;     end;     close(input); end;   procedure Work; var     k,i,j   :byte;     W   :array [1..MaxSize,1..MaxSize] of word;     P   :array [1..MaxSize,1..MaxSize] of byte;     procedure GetMaxPath;   {求最大费用路,用Bellman-Ford迭代法}     var         i,j :byte;         t   :integer;         Finish  :boolean;   {迭代终止标志}     begin         fillchar(W,sizeof(W),0);         repeat             Finish:=true;             for i:=Row downto 1 do                 for j:=Col downto 1 do                 begin                     if i<Row then                     begin   {横向边}                         t:=W[i+1,j];                         if F[i,j,1]=0 then                             inc(t,Vert[i,j]);                         if t>W[i,j] then                         begin                             W[i,j]:=t;                             P[i,j]:=1;                             Finish:=false;                         end;                         if F[i,j,1]>0 then                         begin   {横的逆向边}                             t:=W[i,j];                             if F[i,j,1]=1 then                                 dec(t,Vert[i,j]);                             if t>W[i+1,j] then                             begin                                 W[i+1,j]:=t;                                 P[i+1,j]:=3;                                 Finish:=false;                             end;                         end;                     end;                     if j<Col then                     begin   {纵向边}                         t:=W[i,j+1];                         if F[i,j,0]=0 then                             inc(t,Horiz[i,j]);                         if t>W[i,j] then                         begin                             W[i,j]:=t;                             P[i,j]:=0;                             Finish:=false;                         end;                         if F[i,j,0]>0 then                         begin   {纵的逆向边}                             t:=W[i,j];                             if F[i,j,0]=1 then                                 dec(t,Horiz[i,j]);                             if t>W[i,j+1] then                             begin                                 W[i,j+1]:=t;                                 P[i,j+1]:=2;                                 Finish:=false;                             end;                         end;                     end;                 end;         until Finish;     end; begin     fillchar(F,sizeof(F),0);     Result:=0;     for k:=1 to N do{求的是流量为N的最大费用流}     begin         GetMaxPath;         inc(Result,W[1,1]); {累计费用}         i:=1;j:=1;         repeat  {更新最大费用路上的流量}             case P[i,j] of                 0:  begin                         inc(F[i,j,0]);                         inc(j);                     end;                 1:  begin                         inc(F[i,j,1]);                         inc(i);                     end;                 2:  begin                         dec(j);                         dec(F[i,j,0]);                     end;                 3:  begin                         dec(i);                         dec(F[i,j,1]);                     end;             end;         until (i=Row) and (j=Col);     end; end;   begin     assign(input,'input.txt');     assign(output,'output.txt');     ReadIn;     Work;     rewrite(output);     writeln(Result);     close(output); end.   【参考文献】 1.胡运权 郭耀煌 《运筹学教程》 清华大学出版社 1998.6. 2.张辰《“花店橱窗布置问题”解题报告》(2000集训队第一次作业) 3.张辰《“机场跑道”解题报告》(2000集训队第一次作业) 4.潘金贵 等 《现代计算机常用数据结构和算法》 南京大学出版社 1994.3. 5.解可新 韩立兴 林友联 《最优化方法》 天津大学出版社 1997.1. 6.《“障碍物探测器”试题》 IOI’98中国集训队冬令营特刊1998.1. 7.《“迷宫改造”试题》 信息学奥林匹克1999年第1期 8.倪兆中 《“障碍物探测器”题解》 信息学奥林匹克1998年第1-2期
动态规划的特点及其应用 动态规划是信息学竞赛中的常见算法,本文的主要内容就是分析 它的特点。 文章的第一部分首先探究了动态规划的本质,因为动态规划的特 点是由它的本质所决定的。第二部分从动态规划的设计和实现这两个 角度分析了动态规划的多样性、模式性、技巧性这三个特点。第三部 分将动态规划和递推、搜索、网络流这三个相关算法作了比较,从中 探寻动态规划的一些更深层次的特点。 立即下载

相关推荐

基于STM32环境温湿度监测系统设计

基于STM32环境温湿度监测系统设计1. 实验目的2. 任务和要求3. proteus仿真图4. 实验代码 1. 实验目的 (1) 掌握使用 KEIL MDK5 进行软件开发的具体流程; (2) 掌握硬件电路的设计方法; (3) 掌握 STM32 微控制器的程序设计及代码编写方法。 2. 任务和要求 (1)在 STM32F103 平台下,通过温度传感器 DS18B20 和湿度传感器 DHT11 实现环境温湿度信息的采集; (2)将温度传感器和湿度传感器所采集的数据在 LCD 液晶屏幕上显示出来,超

唯一的博客 2万+

算法—动态规划简单介绍

1.动态规划的定义: 动态规划是分治思想的延伸,通俗一点来说就是大事化小,小事化无的艺术。 在将大问题化解为小问题的分治过程中,保存对这些小问题已经处理好的结果,并供后面处理更大规模的问题时直接使用这些结果呢。 2.动态规划的特点: (1)把原来的问题分解成了几个相似的子问题。 (2)所有的子问题都只需要解决一次。 (3)储存子问题的解。 3.动态规划的本质: 是对问题状态的定义和状态转移方程的定义(及状态和状态之间的递归关系) 4.动态规划问题的考虑角度: (1)状态定义...

a1085653724的博客 1635

什么是论文的创新点?公式化描述算是创新点吗?

写科研论文时,挑战之一就是如何让你的工作“脱颖而出”,也就是要有创新性。不管实验多么精确、推导多么复杂,若没有真正的创新,论文很可能会被拒。关于如何创新问题上,我最近在看刘雪峰老师的《科技论文写作指南》,看到了很多关于写作的新论述,在这里和大家进行分享。

加百力 的技术博客 1927

动态规划

动态规划动态规划的基本思想动态规划的特点动态规划的本质适用场景例一:斐波那契数列例二 变态青蛙跳台阶新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLo...

你又来看我了,一起学习吧 1323

实战指南:在华为防火墙上构建基于自签CA的SSL证书挑战式认证体系

本文详细介绍了在华为防火墙上构建基于自签CA的SSL证书挑战式认证体系的实战指南。通过自签CA证书生成、华为防火墙SSL VPN配置、客户端安装与测试等步骤,帮助企业实现更安全的远程办公接入方案,有效防范密码泄露风险。文章重点讲解了证书挑战登录的实现原理和配置细节,适用于企业网络安全建设。

weixin_32428571的博客 257

Elsevier期刊LaTeX双栏模板(cas-dc)实战:从Overleaf配置到投稿全流程

本文详细介绍了Elsevier期刊LaTeX双栏模板(cas-dc)在Overleaf平台上的配置与使用全流程,包括模板结构解析、标题与作者信息规范、摘要与关键词特殊格式、正文写作技巧、参考文献管理以及投稿前检查清单。特别针对Elsevier期刊投稿需求,提供了从模板安装到最终投稿的实用指南,帮助研究者高效完成论文排版工作。

weixin_28455801的博客 1323

算法思想:动态规划

动态规划比较适合用来求解最优问题,比如求最大值、最小值等等。它可以非常显著地降低时间复杂度,提高代码的执行效率。不过,它也是出了名的难学。它的主要学习难点跟递归类似,那就是,求解问题的过程不太符合人类常规的思维方式。对于新手来说,要想入门确实不容易。不过,等你掌握了之后,你会发现,实际上并没有想象中那么难。 0-1 背包问题 对于一组不同重量、不可分割的物品,我们需要选择一些装入背包,在满足背包最...

shayne000的博客 459

基于simulink的低通滤波器(Low-pass Filter, LPF)

摘要:本文详细介绍了如何在Simulink中搭建低通滤波器进行噪声抑制仿真。通过定义系统需求(50Hz信号+250Hz噪声)、添加信号源、设计6阶巴特沃斯低通滤波器(截止频率100Hz)、连接各模块并配置仿真参数,最终实现高频噪声的有效滤除。该教程包含从模型创建到结果分析的完整流程,为信号处理系统开发提供了基础框架,并可进一步扩展为自适应滤波等高级应用。所需MATLAB版本为R2023a及以上,需安装Simulink和DSPSystemToolbox。

amy_mhd的博客 1303

动态规划的简单理解及例子

动态规划是分治的延伸,通俗一点来说就是大事化小,小事化无的艺术,在将大问题化解为小问题的分治过程中,保存对这些小问题已经处理好的结果,并供后面处理更大规模的问题时直接使用这些结果。 1、动态规划具备了以下三个特点: (1)把原来的问题分解为几个相似的子问题 (2)所有的子问题都只需要解决一次 (3)储存子问题的解 2、动态规划的本质就是对问题状态的定义和状态转移方程的定义(状态以及状态之间的递推关系) 3、动态规划问题一般从以下四个角度考虑: (1)状态定义(定义的状态一定要形成递...

m0_53677355的博客 1471

超详细!Linux 服务器 6 大核心性能测试(附自动脚本 + 结果分析秘籍)---Linux 服务器性能测试文档

本文档旨在提供 Linux 服务器的 CPU、内存和磁盘性能测试方案,包括详细的测试大纲、操作步骤以及一键化测试脚本,帮助管理员快速评估服务器性能状况。通过以上测试方案,可以全面评估 Linux 服务器的 CPU、内存和磁盘性能,为系统优化、容量规划和问题排查提供重要参考依据。使用 sysbench 进行 CPU 基准测试。使用 sysbench 测试内存读写性能。使用 fio 工具进行更全面的磁盘测试。使用 dd 命令测试磁盘顺序读写性能。使用 dd 命令测试内存读写速度。记录磁盘 IO 性能。

jundao1997的专栏 381

【数据结构】期末考试复习(考点+例题)

1.线性表定义及其存储实现:顺序表和链接表,优缺点分析(顺序存储可随 机访问,链式存储可灵活利用空间),相关操作的时间复杂度。 2.链表的插入删除操作 3.两种特殊的线性表及其应用:栈、队列 4.栈、队列上的(插入/删除)操作及其特点 5.循环队列满和空的条件 6.能够定义线性表、栈、队列的数据存储结构,能够实现相关操作的算法并 分析时间复杂度(例如插入、删除及其它应用等) 7.理解线性结构的应用( 如根据要求能灵活定义线性表结构,能根据线性表 的插入、删除操作解决实际问题) 二叉树基本概念与性质(高

Molly 的博客 9371

动态规划的问题特性与解题框架

动态规划的问题特性与解题框架

温涛的博客 1854

AI系统架构原理与代码实战案例讲解

人工智能的概念可以追溯到上世纪50年代,图灵测试的提出标志着人工智能作为一门学科的诞生。早期的研究主要集中在符号推理、专家系统等领域,取得了一些突破,例如机器定理证明、西洋跳棋程序等。上世纪80年代,随着专家系统的兴起,人工智能进入了一个黄金时期。专家系统通过模拟人类专家的知识和经验进行推理和决策,在医疗诊断、故障诊断等领域取得了成功应用。进入90年代,由于计算能力的限制和数据量的不足,人工智能的发展陷入低谷。近年来,随着深度学习技术的突破、大数据的兴起以及计算能力的提升,人工智能迎来了第三次发展浪潮,在图

AI天才研究院 1794

历年IOI国家集训队论文题目

原文:http://blog.chinaunix.net/uid-21712186-id-1818211.html   中国国家集训队论文集目录(1999-2009)      国家集训队1999论文集 陈宏:《数据结构的选择与算法效率——从IOI98试题PICTURE谈起》             来煜坤:《把握本质,灵活运用——动态规划的深入探讨》             齐鑫:《

little_胖的专栏 1241

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

动态规划详解(Dynamic Programming)

动态规划的核心思想是将问题分解为一系列子问题,并通过记忆化或递推的方式求解子问题,从而得到原始问题的解。

2726

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

3-2 动态规划算法总结

同一个问题可以有多种方式刻画它的最优子结构,有些表示方法的求解速度更快(空间占用小,问题维度低)利用问题最优子结构性质,以自底向上的方式通过子问题的最优解逐步构造出整个问题的最优解。(一)动态规划可解决的问题同分治策略解决的问题类似,区别是存在。(1)找出最优解的性质,并刻划其结构特征(最优子结构性质)。矩阵连乘计算次序问题的最优解包含着其子问题的最优解。先假设由原问题的最优解导出的子问题的解不是最优的。(4)根据计算最优值时得到的信息,构造最优解。的求解速度更快(空间占用小,问题的维度低)。

我走的每一步都算数 783

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

动态规划概念、特点、经典例题和于其它算法思想的比较

目录1.动态规划概念2.动态规划适用问题的特点2.1 最优子结构2.2 重复子问题2.3 无后效性3. 动态规划的例子3.1 简单例子——Fibonacci数列3.2 经典例子——背包问题4 动态规划解题思路和方法5 动态规划和贪心、回溯、分治算法的比较5.1 动态规划5.2 贪心5.3 回溯5.4 分治 1.动态规划概念 动态规划(Dynamic Programming,DP)是通过把原问题分解成相对简单的子问题的方式来解决复杂问题的方法。它的基本思想是将待求解问题分解成不同部分(即子问题),然后依据子问

Blateyang的博客 1万+

常见面试题--动态规划介绍(附C++源码实现)

它的基本思想是将问题分解成若干个子问题,先求解子问题,并将这些子问题的解存储起来,以便在解决更大问题时重用,从而避免重复计算。动态规划算法适用于具有重叠子问题和最优子结构性质的问题,这类问题在分解后得到的子问题往往不是互相独立的,而是存在依赖关系。通过将一个大问题分解成若干个小问题,并逐步解决这些小问题,最终得到原问题的解。:如果问题的最优解所包含的子问题的解也是最优的,则称该问题具有最优子结构性质。以下是一个简单的动态规划(DP)问题的C++实现例子,例子中解决的是子序列和问题。博主其它经典原创:《

借雨醉东风的博客 1761

深度剖析动态规划算法:原理、优势与实战

这篇文章概述了动态规划的基本概念、核心原理、优势以及三个实际应用案例,其中包括最长公共子序列、背包问题和最长递增子序列。文章的结构清晰,对动态规划的应用进行了详细说明,包括Python和Java示例代码。

牧魂的博客 1832

五大经典算法-动态规划 及其算法应用

整篇文章分析整个动态规划算法,什么是动态规划,及动态规划算法在字符串匹配中使用、分治法的差别点、动态规划优点;

踩踩踩从踩的博客 4879

高德地图动态绘制路线--轨迹巡航(vue)

1、将高德地图引入项目 第一步:在index.html文件中引入 <script type="text/javascript" src="https://webapi.amap.com/maps?v=1.4.15&key=自己申请的key"></script> <script src="//webapi.amap.com/ui/1.0/main.js">...

Lycoriy的博客 9473

动态规划的总结

本文链接:https://blog.youkuaiyun.com/zw6161080123/article/details/80639932 已知问题规模为n的前提A,求解一个未知解B。(我们用An表示“问题规模为n的已知条件”) 此时,如果把问题规模降到0,即已知A0,可以得到A0->B. 如果从A0添加一个元素,得到A1的变化过程。即A0->A1; 进而有A1->A2; A2->A3; …… ; Ai->Ai+1. 这就是严格的归纳推理,也就是我们经常使用的数学归纳法; 对于Ai+1,

qq_43510083的博客 1018

算法之路:动态规划(一)

学习动态规划后写的练习题。

wjmhlh的博客 730

动态规划的特点

动态规划是在本世纪50年代初,为了解决一类多阶段决策问题而诞生的。多阶段决策问题:  多阶段决策过程,是指这样的一类特殊的活动过程,问题可以按时间顺序分解成若干相互联系的阶段,在每一个阶段都要做出决策,全部过程的决策是一个决策序列。要使整个活动的总体效果达到最优的问题多阶段决策问题包含两个要素:阶段和决策阶段:将所给问题的过程,按时间或空间特征分解成若干相互联系的阶段,以便

crazyboy2005的专栏 5611

详解动态规划算法(Python实现动态规划算法典型例题)

动态规划(Dynamicprogramming) 是一种在数学、计算机科学和经济学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。 什么是动态规划 动态规划(Dynamic Programming)对于子问题重叠的情况特别有效,因为它将子问题的解保存...

一个平平无奇的甜妹 4万+
上一篇: Edubuntu 强劲功能截图
下一篇: QPL1-9
Everstray
博客等级 码龄19年 8粉丝 16原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值