动态规划之最长上升子序列模型

dp给我的感觉就相当于dfs。只不过dp是从一个集合推到另一个集合。在思考dp的时候可以思考一下如果dfs的话,它是如何推出答案的。也就是说,我们不用关心具体的dfs过程,但要知道它是如何从无到有,算法推进过程中得到中间结果的顺序是什么。把这些中间结果放到一个集合中,就是dp。

背包模型和最长上升子序列模型的区别,在于最优的区别。最长上升子序列模型因为它们本身的性质最优,而背包模型最优不是因为它们本身的性质,而在于外界给予它们的约束条件。就比如,最长上升子序列模型的最优子序列,本身就满足最长、最短、和最大、花费最小……而背包模型的最优子序列,你的序列最优,是因为你的总体积<=v,序列再长、再短、和在大、体积在大都没用。

有些题目,让求给定序列的最长的子序列,这个子序列是***形状的。(序列因为它们本身的性质,所以最优)

五一到了,ACM队组织大家去登山观光,队员们发现山上一个有N个景点,并且决定按照顺序来浏览这些景点,即每次所浏览景点的编号都要大于前一个浏览景点的编号。
同时队员们还有另一个登山习惯,就是不连续浏览海拔相同的两个景点,并且一旦开始下山,就不再向上走了。
队员们希望在满足上面条件的同时,尽可能多的浏览景点,你能帮他们找出最多可能浏览的景点数么?

自己的做法
题目让求最长的子序列,该子序列满足单调递增或者先递增后递减或者递减。
我们要得到最长的子序列,就需要枚举出A(1, n)中的所有的子序列(不用关心如何枚举)。我们把枚举过程中得到的一类中间子序列放到集合中。

为了表示更精确的划分集合,我们在集合表示时,需要用某一种方式,来对集合里的内容做更详细的描述。我们用0表示单调递增,1表示单调递减。

大部分集合只涵盖了算法推进过程中的中间序列
集合{i, 0}:以a[i]结尾,并且该集合中的所有序列从头元素单调递增至a[i]或者只包含a[i]

集合划分:看倒数第二个数。根据集合的定义,集合中的所有序列,倒数第二个数可能不存在;如果存在,因为单调递增,所以a[k] < a[i]。

集合{i, 1}:以a[i]结尾,并且该集合中的所有序列在某一段连续单调递减至a[i]或者只包含a[i]。

集合划分:看倒数第二个数。倒数第二个数可能不存在;如果存在,根据集合的定义,因为所有序列在某一段连续单调递减至a[i],所以倒数第二个数a[k]肯定小于a[i]。

网上的做法
集合定义稍不一样。
我们要求出题目所需要的序列。题目所需的序列肯定有一个顶点。我们枚举出以这个顶点结尾的所有递增子序列,再枚举出这个顶点开头的所有递减子序列,然后拼接,就得到答案。

因此设计两个集合:(所有集合里的内容涵盖了算法推进的中间序列和目标序列
dp_Up[i]:以a[i]结尾的最长递增子序列
dp_Down[i]:以a[i]开头的最长递减子序列
属性都是:max Len

因此dp_Up[i] + dp_Down[i] - 1就是我们想要的结果。

总结
自己的思路比较偏暴力一点。如果让我去暴力做这个题,我会直接枚举出所有子序列。
网上的思路的方法巧妙一点。如果按网上的思路暴力枚举,肯定是先枚举每个顶点,然后枚举以这个顶点结尾的所有递增子序列以得到最长递增子序列(目的),再枚举这个顶点开头的所有递减子序列,以得到最长递减子序列。


有的题目需要转化:

N 位同学站成一排,音乐老师要请其中的 (N−K) 位同学出列,使得剩下的 K 位同学排成合唱队形。     
合唱队形是指这样的一种队形:设 K 位同学从左到右依次编号为 1,2…,K ,他们的身高分别为 T1,T2,…,TK ,  则他们的身高满足 T1<…Ti+1>…>TK(1≤i≤K) 。     
你的任务是,已知所有 N 位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

题目可以等价转化为,求最长的合唱队形。而合唱队形就是上一个题目让求的子序列。


Palmia国有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的N个城市。
北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同。
每对友好城市都向政府申请在河上开辟一条直线航道连接两个城市,但是由于河上雾太大,政府决定避免任意两条航道交叉,以避免事故。
编程帮助政府做出一些批准和拒绝申请的决定,使得在保证任意两条航线不相交的情况下,被批准的申请尽量多。

自己的做法
题目等价于让求最长的键值对序列。如果我们把南岸和北岸的城市画出来,把每个键值对当成一个斜线,题目等价于让求这些斜线序列的最长不相交子序列。
我们要枚举出最长的斜线序列,必须要按某一顺序枚举出所有的斜线序列。我们可以按南岸坐标(或者北岸)从小到大枚举斜线序列。将枚举过程中的合法中间斜线序列放到集合中:

dp[i]:以a[i]结尾的斜线序列集合。
属性:MAX LEN

集合划分:
按倒数第二个数进行划分。

网上的做法

将键值对按南岸序列从小到大排好后,等价于再去求北岸序列的最长上升子序列。


有些题目,会给你两个序列,然后根据这两个序列求一个最优序列。(不一定是子序列)

给定两个字符串 A 和 B ,现在要将 A 经过若干操作变为 B ,可进行的操作有:
删除–将字符串 A 中的某个字符删除。
插入–在字符串 A 的某个位置插入某个字符。
替换–将字符串 A 中的某个字符替换为另一个字符。
现在请你求出,将 A 变为 B 至少需要进行多少次操作。

题目等价于让我们求一个包含删除,插入,替换的操作序列,这个操作序列,可以将A变为B,并且序列长度最短。

我们要将A变为B,肯定要从无到有,列举操作。因此,在算法推进的过程中,肯定有一个没列举完的操作序列(a[0]替换,在a[0]后插入……这样的操作),这个操作序列,可以将A的一部分变为B的一部分。
{i,j}:将A[1……i]变为B[1……j]的所有操作序列集合。
属性:MIN len

集合划分:对于集合中的所有操作序列看最后一个的操作

第一类,a[i] != b[j]

  1. 如果最后一个操作是删除a[i],然后将A[1……i]变为B[1……j],那么这类操作序列,如果“遮掉” "删除a[i]"这个操作,投影出来的操作序列肯定可以把A[1……i - 1]变为B[1……j]。这类操作序列的最小长度,就是“遮掉”"删除a[i]"操作后,操作序列的最小长度,再加上1。
  2. 如果最后一个操作是替换a[i],替换的值肯定是b[j]。那么遮掉这个操作后的操作序列,应该能够把A[1……i - 1]变为B[1……j - 1]。
  3. 如果最后一个操作时在a[i]后插入,那么插入的值肯定是b[j]。遮掉这个操作的操作序列,可以把A[1……i]变为B[1……j - 1]。

第二类, a[i] == b[j]

  1. 如果a[i] == b[j],那么实际上不用对a[i]进行任何操作。这堆操作序列不用遮掉,就能够将A[1……i - 1]变为B[1……j - 1]。因此直接包含在{i - 1, j - 1}中。

最长公共上升子序列

如何获得状态:算法推进。要得到最长用重复去求它。
公共上升子序列,你必须通过某种搜索过程,得到全部的公共上升子序列;将搜索过程中得到的这些子序列分类划分成不同的
集合;保存这些集合的属性,使我们不用重复去求。

在算法推进过程中,我们按某一顺序同时遍历数组A和数组B,会获得一堆公共上升子序列。我们要从这些公共子序列中得到最长公共子序列。

将这些公共子序列划分:
dp[i][j]:序列包含在A[1……i]和B[1……j]中,且以B[j]结尾的子序列长度。

1.对于不包含A[i]的子序列,也就是同时位于{i - 1, j}集合,其最长公共子序列的长度为dp[i - 1][j]
2.对于包含A[i]的子序列,因为A[i]是A的最后一个元素,B[j]是B的最后一个元素,序列同时含有A[i]和B[j],所以肯定有A[i] == B[j]。
对这些子序列进行进一步的划分:遮掉B[j],然后进行映射,可以得到一些子序列。这些子序列肯定全部位于{i - 1, k}集合(这些集合可以
为空集)中(k是倒数第二个元素的位置)。因此,这些公共子序列的最长长度为:dp[i - 1][k] + 1。

得:dp[i][j] = max(dp[i - 1][j], dp[i - 1][k] + 1 (0 <= k < j) )

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值