对动态规划问题的一些思考

HDOJ刷了一些动态规划(DP)问题,做些总结.

1 序列

目前看来,所有的动态规划问题基本上都存在一个序列,序列中的元素可以按照某种顺序唯一地排序.更抽象地说,排序的方式通常是这些元素在某个事件发生过程的时间轴上的位置.

我们说”序列”而非”集合”,是因为元素的唯一排序方式是非常重要的.因为这样我们就可以将精力集中于组合而非排列.在我看来,动态规划问题在本质上是一种组合问题,这样的问题总是可以通过枚举序列元素的全部组合而得以解决.

问题中的序列有时很明显,有时不明显(2059),有时是二维的(2571),甚至干脆没有显示给出(2084).不管怎样,我认为解决动态规划问题的首要任务就是明确这个序列.序列明确以后,我们就可以直接”腰斩”这个问题并考虑其”下半身”,即直接从序列的第i个元素开始思考问题.

2 问题的表示

由于动态规划问题总是关联着一个序列,因此我们可以用序列表示这个问题.比如用[0:]表示原问题,用[i:]表示从问题的”下半身”.就像在物理中我们总是将物体抽象成一个质点一样,将动态规划问题抽象成一个序列对我们进一步思考问题有很大的帮助.

当然,没必要煞有介事地用上[i:]这个类似于切片的语法,直接用数字0表示原问题也未尝不可–这里的关键是内容,而非形式.我没有正式地对符号进行定义,因为接下来的讨论大多是描述性的,严格定义符号显得过分隆重.

另一方面,用[0:]这样的形式表述问题是不全面的,因为很多问题的完整描述需要额外的变量.比如对于完全背包问题,当序列是[0:]时,我们需要额外的一个变量w来表示背包的剩余容量.这样对问题的完整表述应该是([0:],w),或者随意一些,(0,w).w是必要的,(0,w)(0,x)表示的是同一问题在两个不同状态下的解,或者干脆就是两个不同问题.

这样,对于一个问题,我们既可以将其表示成[i:]也可以表示成(i,j).尽管符号的使用非常随意,但是引入一些符号确实能帮助我们更方便地分析问题.下面是我在分析问题时使用的一些符号.

符号意义
a[i]序列的第i个元素
[i:]i号元素开始的序列,或者与这个序列相关的问题
(i)思考问题[i:]时需要枚举的集合,或者第i个决策阶段,详见后面
(i,j), (i,j,k,…)问题[i:]的另一种表述方式,此时我们强调表述问题的额外变量j,k
ti,j[x]问题(i,j)在决策x下的局部最优解,详见后面
s[i,j]问题(i,j)的解

无论如何,就像我刚才说的,这里重要的是内容而非形式.

3 找到序列

大多数情况下,找到问题的序列是容易的.事实上,我们几乎总是可以凭借直觉准确地找到决定问题的那个序列.在一些罕见的直觉失效的情况下,我们可以用下面这种方式来寻找这个序列.

第一种方式是将问题视为一个枚举问题,我们只要枚举某些元素就能够解决问题.在这种情况下,被枚举的元素构成这个序列.2059就是典型的这种问题,乌龟只要从所有的充电站中选择合适的若干个来充电,就能在最短的时间内跑到终点.因此,它只要枚举所有的选择方案就能解决这个问题.被枚举的对象是充电站的组合,那么充电站的序列自然而然的成为了与这个问题关联的序列.

第二种方式是将问题视为一个分阶段的决策问题,每个阶段都关联着问题中的一个元素,此时,所有阶段的所有元素构成与这个问题相关的序列.完全背包问题是一个典型的决策类动态规划问题.在第i个阶段,我要选择ji号物品,然后进入第i+1个阶段,进行i+1号物品的选择.这样物品的编号就是与这个动态规划问题关联的序列.

事实上,我们将会看到,将动态规划问题视为枚举或决策问题的思想对于解决整个动态规划问题都十分有效,我们仅仅使用这种思想来寻找问题的序列有点”杀鸡用牛刀”的感觉.

无论如何,现在我们拥有一个序列了.接下来我们要面临真正的考验,即解决问题[i:].正如我刚才所说,思考这个问题的切入点正是将问题视为一个枚举或决策问题.

4 枚举问题

有些问题可以被非常自然地理解为一个枚举问题.此时,我们将为了解决问题[0:]而需要枚举的集合记为(0),这样我们的从集合(i)开始思考问题.有两种思考问题的方式.不管采用哪种方式,我们的目的都是为了用集合(i+1)将集合(i)表示出来.

第一种方式是直接用序列中的元素将集合(i)表示出来,然后重复同样的步骤以得到集合(i+1).接下来,只要观察这两个集合,就可以找到它们的关系.1003是典型的这种问题.

第二种方式采用了归纳法的思想.在这种情况下,假设对于所有的整数x>0,集合(i+x)已知,然后我们想方设法用(i+x)(i)表示出来.

无论用那种方式,最终我们会得到(i)(i+x)的表达式.通常是下面这个样子:

(i) = AUBUC…

其中A,B,C是由(i+x)导出的集合.

这样,我们就可以通过考察(i)(i+x)的关系来找到s[i]s[i+x]的关系.当然,s[i]s[i+x]并不是总是存在关系,甚至,(i)并非总是能够用(i+x)表示.但是对于动态规划问题,我们有充分的理由来保持乐观的态度.

5 分阶段决策问题

另一些问题很容易被视为一个分阶段决策问题,典型的如完全背包问题,以及1176,2084,2571等.这个时候我们对[i:]的思考非常直接.

从阶段(i)开始,我们做出一个决策x,然后进入阶段(i+1),即问题[i:]在决策x下变成了问题[i+1:].这个时候,我们使用强调全部变量的方式来表述问题,因此这个变化可以记为:

(i,j) -x-> (i+1, j*)

观察这个变化,我们就可以进一步找出ti,j[x]s[i+1,j*]的关系,通常可以表示成下列形式:

ti,j[x] = f(a[i], s[i+1, j*])

问题(i,j)的解可以通过考察所有的ti,j[x]得到:

s[i,j] = g(ti,j[x1], ti,j[x2],…)

注意,做出决策x未必一定会跳转到阶段(i+1),比如跳棋问题,以及1260等.只不过,无论跳转到(i+1)还是(i+n),思考问题的方式是一样的,而(i+1)是普遍情况并且更容易表示和理解,因此我使用(i+1)而非更一般的(i+x).

6 补充说明

关于枚举和决策问题,有必要做出以下两点补充:

第一,枚举问题和分阶段决策问题的界线是模糊的.我们既可以强行以枚举的方式解决决策问题,也可以强行以决策的方式解决枚举问题.只是,将有的问题视为枚举问题确实比把它视为决策问题要自然一些,反之也是如此.

第二,解决决策问题比解决枚举问题要容易地多.事实上,当找到集合(i)(i+x)的关系以后,接下来将集合结构向决策结构靠拢是一个十分有效的思路.

7 什么样的问题不是动态规划问题

似乎我们有一些手段可以判定一个问题不是动态规划问题,至少,下面这两类问题看上去很难归结为动态规划问题:

  1. 不能用枚举的方式解决的问题,例如12091257;
  2. 要枚举所有排列才能解决的问题.如25441789.

特别地,考虑到动态规划问题是一些组合问题,即这类问题的本质是从整体中选择若干个元素.因此如果问题的解一定由全部元素构成(比如问题的解是全部元素的某个排列),那么这类问题不太可能是一个DP问题.

虽然2544作为最短路径问题确实有动态规划解法,但是我倾向于将这个问题划归为非DP问题,原因如下:

  1. 2544的DP解法在本质上与dijkstra算法是一样的,而dijkstra算法通常被视为贪心算法;
  2. 2544不具备动态规划问题的一些常见特征,如序列特征(你无法将图中的节点排成一个唯一序列),组合特征(相同节点的不同排列构成不同的路径).

其次,如果如果全排列的个数比较少(通常少于16!),那么第二类问题有时候可以用动态规划来解,事实上这是动态规划问题的一大子类,叫做状态压缩动态规划.

8 不足之处

上述讨论只是非常粗略,抽象,浅显地讨论了解决动态规划问题的一些思考方式.算是对上述28个问题的一些高度很底的总结.但是这些总结的直接作用是非常有限的.事实上,上述总结甚至不能指导你完整地解决完全背包问题和状态压缩问题.

不仅如此,有很大一部分动态规划问题具有巨大的特殊性,以至于它们几乎完全与上述总结格格不入.如最长公共子序列问题(1159).

而另一部分动态规划问题虽然可以纳入上述思路,但是其最优子结构或者重复子问题是如此难以寻找,以至于我们即使完全照搬上述思路也依然还是会陷入束手无策的境地,例如1421,这是一个最优子结构很难寻找的问题.更进一步,如果不是已经被模型化了,那么01背包问题和完全被背包问题完全可以成为重复子问题难以寻找的典型.

9 后记

正如我刚才所说,每个动态规划问题都有其特殊性,或者很难找到最优子结构,或者很难找到重复子问题.我想,也许只有数学家才能给出一种简洁优雅的表示方法,但那必是极度抽象的.而理论越抽象往往越难应用到实际.所以我从不奢求能对动态规划问题总结出一种”大统一”的东西.

上面这些所谓的”总结”,一非正式,二非全面,三非高人之笔,对一些人而言,说是毫无价值也不为过.但是我的初衷仅仅是希望将来自己看到这篇文章时,能很快回忆起我此时此刻对动态规划的理解.当然,如果居然有人能够”不厌其烦”地读完,并由此而产生一些自己的想法,那我简直要荣幸至极了.

10 附录

10.1 统计

DP非DP补充合计
234127

10.2 题表

动态规划
#分类遍历对象遍历方式模型特点
1003决策连续子序列特有的决策方式最大连续子序列和问题
1024
1069枚举有序子序列跳转跳棋问题
1074决策排列有状态DFS
1087枚举有序子序列跳转跳棋问题
1114决策数量组合乘法原理完全背包问题
1159特殊子序列特殊最长公共子序列问题
1160枚举有序子序列特转跳棋问题
1171决策数量组合乘法原理完全背包问题
1176决策组合3无状态DFS
1203决策基本组合无状态DFS01背包问题
1231决策连续子序列特有的决策方式最大连续子序列和问题
1260决策基本组合无状态DFS
1284决策数量组合乘法原理完全背包问题
1421
1978决策有序子序列跳转二维问题
2059决策基本组合无状态DFS
2084决策基本组合无状态DFS
2159决策数量组合无状态DFS完全背包问题多限制,三维
2191决策数量组合无状态DFS完全背包问题
2571决策有序子序列跳转
2602决策基本组合无状态DFS01背包问题教科书式背包问题
2709决策数量组合无状态DFS完全背包问题
补充
#分类遍历对象遍历方式模型特点
补充集合m元组选/不选
非动态规划
#模型特点
1029非DP非遍历可解
1257非DP非遍历可解
1789非DP,贪心排列
2544非DP,最短路径问题排列

10.3 源代码

我已经把问题的源代码上传到github上了: https://github.com/tjytlxwxhyzqfw/hdoj/tree/master/06-DP

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
在学习动态规划的过程中,需要多做题目,多思考,并坚持下去。这是一个需要慢慢领悟和体会的过程。 动态规划有点像贪心算法,但是它更注重整体最优解的推导过程。它通过更新最优解的过程,可以实现问题的反悔。同时,动态规划也有一点类似于分治法,将问题划分为各个阶段,并对每个阶段进行求解,最后将结果推向整体。与分治法不同的是,动态规划中的子问题往往不是互相独立的。因此,动态规划通过保存已解决的子问题的答案,避免了大量的重复计算,从而提高了效率。 动态规划是一个较难的知识点,对于初学者来说,可能是一个难过的坎。它是一种神奇的解法,能够以出乎人意料的时间复杂度解决一些难题。但是,动态规划思维性较强,需要正确设定状态,写出准确的状态转移方程,并判断是否存在最优子结构。 关于动态规划题目的数量,根据提供的引用内容,没有具体提到40道题目。因此,我无法回答关于40道动态规划题目的问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [DP动态规划)学习心得](https://blog.csdn.net/weixin_30846599/article/details/99838850)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值