DP的学习

简介:

DP在ACM的算法里面可算是重中之重,题目类型千变万化,题目难度差异也很大.是一种很讲究技巧的算法,而且代码实现相对容易,1y率非常高(除有些bt数据外).总之DP就是一向非常重要,又非常博大精深的算法.我们学校的Roba的大牛在这方面就有很深的造诣.
说一下自己这几天接触的初级DP,DP中最重要的往往是状态和状态之间的转移,找到状态转移方程,用递归或者是递推的方式列出方程,题目也就迎仞而解 了,而所谓的难题往往是初看不知所措,找不出状态转移方程,或者根本连什么是状态的都看不清,下面分析一下有关DP的非常经典的3个问题:
1.求局部最大和
大意是这样给你一个数组,在其中取任意连续多个,使其和要最大.假设这个数组大小是10,一般会想到怎么做呢,一个连续的序列,肯定能被一个st和end唯一的表示出来,遍历所有的可能,求出其中最大的即可.


以下内容为程序代码:
for(st=0;st<n;st++)for(end=st;end<n;end++)for(i=st;i<=end;i++)max+=n[i];


一个3重循环,耗时是10^3,可以在不改变算法思想的基础上加以优化吗,答案是肯定的.
建立一个sum[10]数组表示从数组n 的累加和,这样的话每次计算max只需要一次操作sum[end]-sum[st-1]了,至于sum数组的建立过程,只需要一个O(n)的时间即可做 到,所以总的时间复杂度降为O(10^2);能把复杂度将到O(n)吗?这时候就要用到动态规划的思想了.假设当前状态是在以end结尾的局部最大和,通 过这个能否得到以end+1结尾的局部最大和.想一想,以end+1结尾,可以分为两部分,一个是只有end+1这一个元素,另外就是再加上以end结尾 的局部最大和,说一般了,就是从先前状态最优推出了当先的最优态,具体状态转移方程就是:

以下内容为程序代码:
if(ans[i]<0)ans[i+1]=n[i+1];else ans[i+1]=ans[i]+n[i+1];


再把遍历所有的ans,找出最大的就OK了,这样的话只需要O(n)的时间就能完成.
TOJ1782有道类似的题目,感兴趣的同学可以去看看.
2.最长递增子序列
大意是在一个序列中找到一个最长的递增子序列,求出其长度.例如1 7 3 5 9 4 8 Ans: 4 (1 -> 3 -> 5 -> 8),咋看之下,好象没有合适的做法.遍历?2^n的复杂度,显然是不可行的.通过上面例题,我们可以考虑用DP的方法来解决这类问题.假设一个长度为n 的数组,现在已经知道以从1到i为序列的尾的最大递增子序列,那么要求以i+1为尾的序列的最大递增子序列,只要判断n[i+1]是否大于a[1:i], 然后取其中ans[i]最大的那个加1就是按ans[i+1]的值.最后在遍历所有的ans找出最大的即可.是不是和局部最大和很想象,唯一的不同就在于 在局部最大和中只需要ans[i]即可求出ans[i+1],这是因为n[i+1]的前面只能接n[i]导致的,而这题的ans[i+1]确是由 ans[1:i]共同决定的,这是由于最长递增子序列可以不连续导致的.换句话说,要是这题是求最长递增连续子序列的话,那就和上题的处理几乎一样了.说 到这里,相信大家对DP都有了一个基本的了解了,我的理解是对于有些不知如何下手的题目,确定一个先前状态往往是给了一个已知条件,是问题明朗化了,再找 到当前状态,所以说DP的难点在于状态划分和确定状态转移方程,而我的感觉是往往前者更重要.回到刚才那个题目,内层循环要做的是找到一个小于 n[i+1]的并且长度最长的子序列,既然是查找,那能否找到O(logn)查找复杂度呢.答案是肯定的.具体怎么实现,大家可以查看这个网址:最长递增 子序列的高效算法.上面说的很详细,我也理解的不好.
第三个问题下次再说吧,睡觉去了...
接着上次的内容,第三个经典例题:最长公共子串;
      大意是:有两个字符串A,B,另有一字符串C,若C同时是A,B的子串,则C为公共子串,求出长度最长的公共子串的长度.
      ans[i][j]=0;//if(i==0||j==0)(1)
      ans[i][j]=ans[i-1][j-1];//if(A[i]==B[j])(2)
      ans[i][j]=Max(ans[i][j-1],ans[i-1][j])//if(A[i]!=B[j])(3)
分 析一下(2),(3),对于(2),如果A[i]==B[j],显然ans[i][j]要加1,那么会不会加2呢?如果加2的话,那么说明 A[i],B[j]分别为公共子串两个不同的字符,那么必然有前后之分,前面的那个字符已经对应了一个字符串的尾部,另外一个字符则不可能是公共子串了, 所以对于A[i]==B[j],ans[i][j]加且仅加1.对于(3)采取同样的分析,易知其分别减去A,B尾部的字符,必有一长度不变.
      具体的代码实现可以有两种方式.1.采取递推的实现方式,自底向上采取两重循环,时间复杂度为O(n^2),不过不是所有的状态转移方程都可以这样做,后 面会详细分析.2.自顶向下,逐级递归,但要注意递归层数(据说不能超过7600层),一般情况下都伴随这记忆化,这样能使时间复杂度从指数级别降低到 O(n^2)的级别.至于和递推在时间上的优劣关系,或者说有没有题用其中一种会TLE而另一种会AC,就不清楚了,望达人指教.
      总结一下这三道经典例题.虽然是属于入门级的简单DP,但还是能给我们一点启示的.如何划分状态?有些时候,状态的划分不能仅仅根据答案,还应该加上某些 限制条件,这样的话会更有利于状态的转移.像求局部最大和中ans[i]并不是前i个数列中的局部最大和值,还加上了该局部最大和的尾部必须是n[i], 最长递增子序列也是一样.还有一点是状态的转移条件,有些时候只有一个前驱,而有的时候又有多个前驱,像2.(这也是2.的复杂度要比1.高的原因),这 个要注意.关于这3道题,还有许多的类似问题,像多维局部最大和,最大m子段和等等.TOJ1564,POJ2479,TOJ1633都是相应的题目,感 兴趣的同学可以做一下.     
      回到刚才所说的,什么时候能用递推,什么时候又不能呢?
      先看一下下面这道例题,POJ1088
      大意是:给一个M*N的矩阵,每一点附上一个值h,要求求一条路径,路径上的相邻两点必须在矩阵中前后左右相邻,并且前驱的h值高于后继.很容易想到状态 转移方程:l[i][j]=Max(l[i][j+1],l[i][j-1],l[i+1][j],l[i-1][j])+1//if(h[i] [j+1]>h[i][j]...),那么用递归写的话只需
if(map[i][j]>map[i][j+1])
if(flag[i][j+1]==-1)
a=flag[i][j+1]=search(i,j+1);
else
a=flag[i][j+1];
if(map[i][j]>map[i][j-1])
if(flag[i][j-1]==-1)
b=flag[i][j-1]=search(i,j-1);
else
b=flag[i][j-1];
if(map[i][j]>map[i+1][j])
if(flag[i+1][j]==-1)
c=flag[i+1][j]=search(i+1,j);
else
c=flag[i+1][j];
if(map[i][j]>map[i-1][j])
if(flag[i-1][j]==-1)
d=flag[i-1][j]=search(i-1,j);
else
d=flag[i-1][j];
return Max(a,b,c,d)+1;
      要是用递推怎么写呢,好象不好写吧,结合前面的3个例题(都是用递推写的),可以得出满足写递推式的两个条件:1.起点必须确定,象3.中ans[i] [j]=0//if(i==0||j==0)是现而易见的,而此题在不同的输入下结果是不同的.2.每一点在推出时都能确保其前驱已被推出,在3.中,在 二重循环中,明显在计算ans[i][j]中ans[i-1][j],ans[i][j-1],ans[i-1][j-1]已经确定了,但是此题却不能保 证.
      好了,说了这么多,相信大家对DP都有一个大致的印象了,划分好状态,找出状态转移方程,看能否用递推,不能的话就递归,但记住要记忆化哦.但是有一类 题,和DP很像,但却很难找到转移方程,或者说起状态转移方程只不过是一个遍历的过程.对于这种题只要不和DP弄混,还是很好做的.
      ZOJ1687大意是:一堆石头m个.两队人轮流取石头,每人取的上限都给出,至少要取一个,谁最后取完谁就失败,要你判断哪队能赢,咋看之下,确实没有状态转移,其实根本不用这么复杂,对于第i个人还剩下j块石头直接
for (i=1;i<=a[p];i++)
if (tot - i > 0 && solve(tot-i,next) == 0)
{
   ans[tot][p] = 1;
   return 1; //如果取子后对方会输,则返回1
}
完全是暴力嘛,就这么简单!时间复杂度为O(石头数*人数),对于n队,应该也有同样的做法.

相关文章
|
机器学习/深度学习
简介网络:GAN、CGAN和PIX2PIX
简介网络:GAN、CGAN和PIX2PIX
1026 0
简介网络:GAN、CGAN和PIX2PIX
|
开发框架 数据安全/隐私保护 Android开发
iOS二维码的生成和扫码详细介绍(手把手教)
iOS二维码的生成和扫码详细介绍(手把手教)
977 0
|
前端开发 JavaScript C++
一文彻底搞懂react hooks的原理和实现
一文彻底搞懂react hooks的原理和实现
714 92
|
运维 Cloud Native 关系型数据库
复盘:我在真实场景下对几款主流云原生数据库进行极限性能压测的一次总结!!(建议收藏)
最近几年,云数据库市场日趋繁荣,进入百花齐放、百家争鸣的时代,头部云计算厂商相继推出了自己的数据库产品,特别是亚马逊的Aurora、阿里云的PolarDB、华为云的GaussDB等等。
1199 1
复盘:我在真实场景下对几款主流云原生数据库进行极限性能压测的一次总结!!(建议收藏)
|
存储 缓存 Linux
docker安装教程
从0到1安装docker。
807 0
docker安装教程
|
消息中间件 缓存 数据库
RabbitMQ 七种队列模式应用场景案例分析(通俗易懂)
与发布者进行可靠的发布确认,发布者确认是RabbitMQ扩展,可以实现可靠的发布。在通道上启用发布者确认后,RabbitMQ将异步确认发送者发布的消息,这意味着它们已在服务器端处理。
RabbitMQ 七种队列模式应用场景案例分析(通俗易懂)
|
编解码 并行计算 算法
PCL关键点检测--NARF关键点
PCL关键点检测--NARF关键点
PCL关键点检测--NARF关键点
|
存储 文字识别 机器人
ABBYY15简体中文汉化包OCR文字识别下载教程
自ABBYY FineReader15新版发布以来,一直好评不断,作为市场领先的OCR文字识别软件可快速方便地将扫描纸质文档、PDF文件和数码相机的图像转换成可编辑、可搜索信息。这也使很多小伙伴开始体验和使用该软件,小编亲自测试安装ABBYY FineReader 15版本,并整理教程,有需要的可以参考下。
825 0
|
存储 弹性计算 负载均衡
【ESSD技术解读-04】ESSD Auto PL规格,引领IO性能弹性新方向
阿里云 ESSD 为云服务器 ECS 提供低时延、持久性和高可靠的块存储服务,成为云厂商全闪块存储的业界标杆。存储团队推出了 ESSD Auto PL 新的云盘规格,把性能与容量解耦,提供 IO 性能按需供给两大关键特性。AutoPL 具备的灵活性和弹性能力降低了 IT 规模规划难度和因规划不当带来的风险,本文详细介绍了Auto PL 新产品特性、揭秘背后的技术原理。
1540 1
|
数据安全/隐私保护 Windows
MindManager2022官方试用版下载安装教程
MindManager 22这个软件是比较有历史的了,多年的开发和迭代,它的功能越来越丰富。最大的亮点就是支持移动设备和在线编辑功能,图形界面算是比较美观的啦,与Office配合工作非常匹配,对于思维导图的高手而言可玩性强。
731 0