zoukankan      html  css  js  c++  java
  • 【一个蒟蒻的挣扎】最长上升子序列

     (被教练压着写完的,嘤)

    感谢机房大佬JYY友情校对

    @Kyoko (组织需要你!!!)

    大佬填坑啦!!!!!!!!!

    博客园老是吞我格式!还吞我字吞我图!看到空白的不对劲的连不上的地方记得刷新

    下面进入正文(文末有推荐练习的模板题两题)(介绍的是如何求出序列长度,我没弄明白这个序列怎么求)


     最长不下降子序列

    是什么呢,就是在一个原序列中,取出一个新序列(不一定连续),其元素单调递增(可以相等)

    即: 原序列为 a [ i ] , 对于其最长不下降子序列,任何一个 i,j (i < j)都有,a [ i ]>= a [ i - 1 ]

    例如:

    原序列 : 1 2 9 8 6 5 4 8 4

    最长不下降子序列:1 2 4 8  (或 1 2 5 8、 1 2 6 8、1 2 8 8)

    长度为: 4

    理解了吗(其实这个应该都学过的)

    过~


    O(n2) 求法

    过于简单不放标程,自己试着写写,普及难度)

    一个数组 F 记录从 i 到 F [ j ] 的最长不下降子序列长度

    两个循环嵌套:一个枚举 i ,一个枚举 j

    最后跑一个循环,找最大值就好

    大概能过 n = 10 的数据

    (想什么呢提高怎么肯能有这么裸的模板这么水的数据啊)

    于是我们进入下一部分也就是重点

    (似乎也不是很难结果看博客就是没有看懂还麻烦了高二的大佬,嘤)


     N log N 求法

    (据说用线段树啊树状数组等数据结构优化什么的,,也能达到这个复杂度,但是啊,我从来都是线段树能不写就不写的

    首先介绍两个STL,非常好用(用于解决这道题)

    (球球你看看它,如果看不懂就先看算法再看它,超级省事的)

    lower_bound与upper_bound

    • 使用二分的思想
    • (所以要求在一个有序的序列内(你乐意的话自己定义一种排序方式也行,但是要有序(不然你也不知道它会出来什么乱七八糟的)))
    • (默认为升序
    • 复杂度大致为 log n

    用法:lower_bound(a+1,a+n+1,x)

             返回 a 数组内 第一个大于等于 x 的数的指针

             令 P = lower_bound(a+1,a+n+1,x)- a,a [ p ] 则 为第一个大于等于 x 的数

             (如果你会指针的话)  * p = lower_bound(a+1,a+n+1,x)也是 第一个大于等于 x 的数

    upper_bound 和它的用法差不多,除了返回的是第一个大于 x 的指针

    (也就是求最大不下降子序列最大上升子序列的差别)

     若我们要求下降序列呢 ? 

     我们可以写一个 cmp,或者使用 C++ 自带的 greater<>() (都在STL里)

    (和 sort 写法差不多)(sort总该写过的)

     lower_bound(a,a+1,x,cmp) / lower_bound(a,a+1,x,greater<>());

    OK!过!进入正式的算法部分

    这个算法使用的是什么思想呢,贪心

    我们定义一个数组 D ,元素依次相等或递增,维护一段不下降子序列(注意!这个 D 数组并非最终的不下降子序列,解释在下面)

    长度为 len,len即为最终需要的最长不下降子序列的长度 ,D [ len ] 即为该区间最小值

    一个循环 i 从1 枚举到 n,将 a [ i ] 逐一加入我们的 D 数组

    由于这个序列一定不下降,所以当 a [ i ] >D [ len ] 时可以直接加在尾端,(len++),那么当 a [ i ] < D [ len ] ,怎么办呢(又不能不加呀(不加肯定要WA呢)

     这时候就需要刚刚花费大量笔墨介绍的 lower_bound与upper_bound 了

    由于是最长不下降子序列,可以相等,故采用 upper_bound ,查找出第一个大于 a [ i ] 的元素 D [ j ] ,将其替换为 a [ i ] 

    至于它的正确性,我就按我的理解随便说说不能当标准证明过程 

    (发现大佬解释比我清楚!于是粘了过来)

    而O(nlogn)的实现过程与这个正好相反。

    因为每次暴力更新都会有很多不必要的比对,比如对于原序列

    1 2 3 4 5

    当前选定的终点是5,在上面的暴力程序中,对于位置5会与1,2,3,4各比较一次,然后得出最后答案。然而,因为这个队列已经是单调递增的,所以5只需要与前一位4比对一次就可以得出答案,从而省去前面3次无用的比对。

    为了避免浪费时间,这里再开一个数组d来存储已经找出的性价比最高的最长不下降子序列。

    这里的“性价比”是指如果采用当前这个子序列作为既定的开头,用于下面继续比对,这样得出的答案一定是最优的

    举例来说:

    对于原序列:

    1 2 5 7 8 1 10

    有下列子序列
    a:1 5 7 8

    b:1 2 5 8 

    称b的性价比高于a,其原理是,对于b数组中相邻两个元素的差要小于a数组中的,而且最后一个元素要比a数组的小,此时称b的性价比比a高。

    而对于性价比更高的序列,再接着处理时,最终所得的结果是最优的。(有最优子结构

    而对于任意一个位置,若在原序列中的a[i]>d[len],其中len为d的长度,那么d[len++]=a[i];

    这个的原理很简单,但是当a[i]<d[len]时,应该怎么处理呢?

    去寻找d数组中第一个第一个大于a[i]的数,让a[i]与该数互换位置,得到性价比更高的序列,这次操作的原因已经在上文中阐述过。

    因为 D 数组是有序的,所以 D [ j ] 一定是D [ 1-j ] 中的最大值,由于我们要求得是最长的序列,根据贪心思想,那么末尾的数越小越好(方便接更多的数),由于 a [ i ] < D [ j ] ,那么将其替换为 a [ i ] 显然更优

    证明:D 数组不是最终的序列

    因为 替换的 a[ i ] ,在原数列中的下标明显大于 D [ j ],所以它的下标不一定小于D[J+1/J+2/J+……n],不满足条件

    证明看不明白无所谓啦,毕竟我们不考证明,了解它的思想指导怎么用就好了嘛(大雾)

    最坏的情况下每次都要二分查找(复杂度为 log 级),一共要往里填 n 次数,故时间复杂度为 O ( n log n)

    代码参考待填充


    一些题外话

    根据我做题时的判断,一般学习算法的时候了解它的思想、实现过程、考场会打就好

    了解它的思想、原理和实现过程,会在你考试做题看到那种莫名其妙的题(感觉啥也不像,只能打莫得分的暴力(暴力也打不好的题我们乖乖放弃就好啦))时能够想到,它的更新过程类似于某种算法,从而想出正解哦!!!开心吗!!!!!!(最近写了两题有感而发)

    至于考场会打,你考场写不出来等于白学!费心费力费头发还莫得好分数,你想想难不难受(我是个蒟蒻只能说出来这些啦)

    学会了这个,你就可以类推 类推求出

    最长上升子序列,最长不上升子序列,最长下降子序列……(都是改个符号就完事)

    然后推荐两道题目,都是很适合练手的,难度也就那样

    洛谷导弹拦截 一道黄题,基本上是个模板,也是书上的例题好像,题解非常棒很值得一看

    这个目前只有题解(我写的版本),不过里面有题面的!!!(我也不知道这题哪里的不过一搜题解还蛮多!)很妙的一题!!

    好的啰嗦了这么久,也就这些啦

    祝我自己,也祝认真看到这里的你

    2019CSP-S,RP++

    ありがとうございます


     最后的最后(抱歉打扰了)

    这篇博客是我看了洛谷的题解以及搜索许多讲解的博客后用自己的思路总结概括的,嗯,就酱

    ——fin

  • 相关阅读:
    UVA10090 数论基础 exgcd
    UVA 10037 贪心算法
    ST表入门学习poj3264 hdu5443 hdu5289 codeforces round #361 div2D
    poj3254状压DP入门
    I.点进来吧,这里有你想要的(01背包)
    J.哭泣的阿木木(线段树模板题)
    可怜的ljb(树状数组,逆序对)
    D武器大师的宝贝(最大相交区间,异或,最大公约数)
    银行排队模拟(队列,模拟,数据结构)
    B
  • 原文地址:https://www.cnblogs.com/Phantomhive/p/11774145.html
Copyright © 2011-2022 走看看