最长上升子序列解决问题:

  有N个数,求出它最长的上升子序列并输出长度。

  在题里不会讲的这么直白,这个算法往往会与其他的算法混在一起使用。


  在这篇文章中不会出现其他的例题,为了让大家更好的理解,我只会对模板进行讲解。(谢谢大家的理解)


  1-朴素算法(时间复杂度炒鸡炒鸡高)

  首先,我们先列出一些无序的数进行观察,例如:1 7 4 2 3 6 8 9 (共8个数)。

  我们通过观察很快可以发现在这个序列中最长的上升序列时1,2,3,6,8,9,长度为6,我们可以把每种情况都遍历一遍,我们现在从1开始,当1为子序列第一个元素的时候,我们1后面比1大的数:7,4,2,3,6,8,9,然后我们一个一个试,如果下一个选择7,那么在寻找7后面比7大的数:8,9,我们选择8,然后再寻找8后面比8大的数9,然后我们选择9,此时后面已经没有元素了,记录下此时的最长上升子序列:1,7,8,9(长度为4),然后回到1,我们已经选择过7了,现在换成4,继续前面的操作,找到序列1,4,6,8,9(长度为5),比原来的4大,所以最长上升子序列更新为5,按照这样的操作,我们最终扫完后就可以得到了最长上升子序列:6;

  这种算法是最容易想的朴素算法,平淡无奇,理所当然,但是不动脑子的代价就是慢!!!(况且也不一定好打)

  我也不上代码了,相信上了大家也不会看(正好也不用打了)。

  2-动态规划V1.0(时间复杂度O(n^2))

  现在进入正题,最长上升子序列的动态规划做法是目前最普遍的,较简便的做法,在对时间复杂度不是特别高的时候,大部分人都会选择这种方法,因为它很好理解也好打。

  首先,作为DP,那么久要有两个要素:状态和转移方程。考虑到看这篇文章的通常都是动态规划的初学者,我在这里提一下动态规划的基本思想:将一个大问题分成多个小问题,通过对很好解决的小问题的求解,得出初始的状态,可以用动态规划解决的题目一般都能找出来从一个状态转移到另一个状态的方法,称为状态转移方程,一般从简单到复杂,数据范围从小到大进行转移,最终通过转移的结果得出最终的最优解往往就是答案(某些数位DP除外),动态规划简单来说就是用各阶段的最优解来推导下一阶段的最优解,相似于记忆化搜索,但是二者又不尽相同。为什么动态规划快呢,因为它减少了很多的不必要的重复搜索过程,以达到剪枝的效果。

  我们想要进行动态规划,要先设出状态,动态规划题中的状态需要和答案有密切的联系,并且可以利用你设的状态推导出转移方程。

  最长上升子序列的问题顾名思义是最长上升子序列的长度,所以我们的f[i]=最长的长度(在动态规划题中习惯用f[]或者dp[]来表示状态数组)而这个i表示什么呢,本题也没什么东西了,只有一串数,那么这个i就表示以第i个数结尾吧。这样我们的状态就出来了,f[i]表示以第i个数字结尾的最长上升子序列的长度。答案输出f[N]即可。

  状态出来了,我们就开始推导转移方程:在推导时我们需要关注的只是这一阶段和它的上一阶段之间的联系(可千万不要往深了想,很可能把自己绕晕),f[i]是以第i个数为结尾的,它的上一阶段只有可能是f[j](其中1<=j<=i-1) 因为f[j]表示的是结尾为第j个数的最长上升子序列的长度,所以在从f[j]推导到f[i]时我们只需考虑是否第j个数小于第i个数,如果小于那么f[i]=f[j]+1(从第j个数为结尾转换成以第i个数为结尾多了一个数,所以长度加1),因为不知道1到i-1中哪个最合适,就都便历一边,最后保留最长的f[i]即可完成这一状态的转移。书写出来就是f[i]=max(f[i],f[j]+1);(max指的是从两个数中选出最大的返回,如果这一状态转移过后还没有原来的f[i]长,那么肯定不转移了呀)。

  现在状态和转移方程都出来了,那么我可以上代码了。(代码十分的好理解)

Code:

  1. #include<iostream>
  2. #include<cstdio>
  3. using namespace std;
  4. int f[]={};
  5. int n,a[]={},ans=;
  6. int main(){
  7. cin>>n;
  8. for(int i=;i<=n;i++){
  9. cin>>a[i];
  10. }
  11. for(int i=;i<=n;i++){
  12. f[i]=;
  13. for(int j=;j<i;j++){
  14. if(a[j]<a[i]){
  15. f[i]=max(f[i],f[j]+);
  16. }
  17. }
  18. ans=max(f[i],ans);
  19. }
  20. cout<<ans;
  21. }

  没啥难以理解的。


  3-动态规划V2.0(时间复杂度O(NlogN))

  为什么我画了一条线呢?

  主要是为了与前面那些笨拙的算法区别开,这种算法比较巧,而且实用。

  在介绍这种算法之前呢,我要先介绍一个C++STL中函数lower_bound(),具体用法附上百度百科的链接https://baike.baidu.com/item/lower_bound/8620039?fr=aladdin

  相信大家都看懂了,下面我来讲下这个动态规划+二分优化的基本思想:我们定义一个数组f[i]这里面的i和上一个1.0的i可不一样了,这个i表示长度为i的最长上升子序列。而f[i]则表示长度为i的子序列的最小结尾。是不是没看懂,那让我来举个例子:例如有1,7,4,2,3,6,8,9 八个数,f[1]=1,因为长度为1的最长上升子序列中只含有一个数1,所以最小一定是1,接着我们看f[2],7比1大,可以将它加入,那么此时i=2,f[2]=7,因为此时的序列是1,7,以7结尾当然7是最小结尾,接着4<7所以我们将4替换到它第一个可以插入的不影响序列顺序的位置,与那个位置上比它大的数交换,更新该位上的f。比如这时,4比1大,比7小,所以应该和7交换,将队列变成1,4,i不变(因为队列长度还是2),所以我们这个算法的思想就是拼命维护长度为i的序列有最小的结尾,这样它就可以在后面容纳更多的数。接着,2>4,所以2将4替换掉,序列变成1,2。i仍然不变化,继续向下推,3小于2,所以i++,将3加入序列最后,此时序列为1,2,3,i=3。6>3,所以加入6,i=4。8>6所以将8加入序列,i=5。接着加入9,i=6。此时已经到了结尾,退出循环,我们看,此时的i就是最长上升子序列的长度了。

Code:

  1. #include<iostream>
  2. #include<cstdio>
  3. using namespace std;
  4. int f[]={},len;
  5. int n,a[]={},ans=;
  6. int main(){
  7. cin>>n;
  8. for(int i=;i<=n;i++){
  9. cin>>a[i];
  10. }
  11. for(int i=;i<=n;i++){
  12. if(a[i]>f[len]) f[++len]=a[i];
  13. else if(a[i]<f[len]){
  14. *lower_bound(f+,f+len+,a[i])=a[i];
  15. }
  16. }
  17. cout<<len;
  18. }

  我们会发现,这种方法和贪心有些像,所以这种方法也可以说是动态规划+贪心优化(动态规划V2.0)。

  好了,这篇文章到这里也该结束了,反正我这么弱,大家看看就好。

简单DP入门(二) 最长上升子序列及其优化的更多相关文章

  1. HDU 1159 Common Subsequence --- DP入门之最长公共子序列

    题目链接 基础的最长公共子序列 #include <bits/stdc++.h> using namespace std; ; char c[maxn],d[maxn]; int dp[m ...

  2. POJ_2533 Longest Ordered Subsequence【DP】【最长上升子序列】

    POJ_2533 Longest Ordered Subsequence[DP][最长递增子序列] Longest Ordered Subsequence Time Limit: 2000MS Mem ...

  3. HDU 2084 数塔(简单DP入门)

    数塔 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submiss ...

  4. 【dp】求最长上升子序列

    题目描述 给定一个序列,初始为空.现在我们将1到N的数字插入到序列中,每次将一个数字插入到一个特定的位置.我们想知道此时最长上升子序列长度是多少? 输入 第一行一个整数N,表示我们要将1到N插入序列中 ...

  5. hdu1243 dp (类最长公共子序列)

    题意:射击演习中,已知敌人出现的种类顺序,以及自己的子弹种类顺序,当同种类的子弹打到同种类的敌人时会得到相应分数,问最多能得多少分. 这题的题意很好理解,而且模型也很常见,是带权值的类最长公共子序列问 ...

  6. hdu1080 DP(类最长公共子序列)

    题意,有两个字符串,分别由四个字母构成,字母之间有不同的相似度,允许在两个字符串都按原顺序排列的情况下进行字母与字母之间的匹配,也可以让字母与空格匹配,即相当于在字符串中间加空格来一一匹配,每个字母与 ...

  7. POJ 1458 Common Subsequence (DP+LCS,最长公共子序列)

    题意:给定两个字符串,让你找出它们之间最长公共子序列(LCS)的长度. 析:很明显是个DP,就是LCS,一点都没变.设两个序列分别为,A1,A2,...和B1,B2..,d(i, j)表示两个字符串L ...

  8. BZOJ 1207 [HNOI2004]打鼹鼠:dp【类似最长上升子序列】

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1207 题意: 有一个n*n的网格,接下来一段时间内会有m只鼹鼠出现. 第i只鼹鼠会在tim ...

  9. DP专辑之最长公共子序列及其变形

    vijos1111(裸的最长公共子序列) 链接:www.vijos.org/p/1111 题解:好久没有写最长公共子序列了,这题就当是复习了.求出最长公共子序列,然后用两个单词的总长度减去最长公共子序 ...

随机推荐

  1. ping局域网主机得到外网IP或另一网段IP

    症状::两个笔记本连接到同一个路由器上, 一个ip是 192.168.1.100,主机名是Lenovo-A, 另一个是192.168.1.109,主机名是Lenovo-B 在Lenovo-A 上pin ...

  2. C#设计模式:工厂模式

    一,工厂模式 using System; using System.Collections.Generic; using System.Linq; using System.Text; using S ...

  3. [转载]Redux原理(一):Store实现分析

    写在前面 写React也有段时间了,一直也是用Redux管理数据流,最近正好有时间分析下源码,一方面希望对Redux有一些理论上的认识:另一方面也学习下框架编程的思维方式. Redux如何管理stat ...

  4. Node.js--fs 文件操作

    process 模块 在使用的时候无需通过 require() 函数来加载该模块,可以直接使用. fs 模块,在使用的时候,必须通过 require() 函数来加载该模块,方可使用. 原因:proce ...

  5. JavaScript中的方法和属性

    书读百遍其义自见 学习<JavaScript设计模式>一书时,前两个章节中的讲解的JavaScript基础知识,让我对属性和方法有了清晰的认识.如下是我的心得体会以及部分摘录的代码. 不同 ...

  6. mapreduce图解系列

    1.Hadoop的hdfs https://www.cnblogs.com/jstarseven/p/7682293.html 2.Hadoop的yarn https://segmentfault.c ...

  7. ES5和ES6数组方法

    ES5 方法 indexOf和lastIndexOf 都接受两个参数:查找的值.查找起始位置不存在,返回 -1 :存在,返回位置.indexOf 是从前往后查找, lastIndexOf 是从后往前查 ...

  8. C/C++ 多线程注意事项

    { 1 父线程和子线程中的内存区是不一样的,如果涉及到堆内存应该注意,否则内存异常比无法解析的外部符号还要恐怖 }

  9. 【Shiro】SpringBoot集成Shiro

    项目版本: springboot2.x shiro:1.3.2 Maven配置: <dependency> <groupId>org.apache.shiro</grou ...

  10. Git 最全命令使用

    git init test 创建并管理一个文件 Git add . 添加到暂存区 Git commit -M '开始的开始' 造了一颗后悔药 Git log 查看版本记录 Git status 查看当 ...