zoukankan      html  css  js  c++  java
  • 最长不下降子序列nlogn算法详解

      今天花了很长时间终于弄懂了这个算法……毕竟找一个好的讲解真的太难了,所以励志我要自己写一个好的讲解QAQ

      这篇文章是在懂了这个问题n^2解决方案的基础上学习。

      解决的问题:给定一个序列,求最长不下降子序列的长度(nlogn的算法没法求出具体的序列是什么)

      定义:a[1..n]为原始序列,d[k]表示长度为k的不下降子序列末尾元素的最小值,len表示当前已知的最长子序列的长度。

      初始化:d[1]=a[1]; len=1; (0个元素的时候特判一下)

      现在我们已知最长的不下降子序列长度为1,末尾元素的最小值为a[1],那么我们让i从2到n循环,依次求出前i个元素的最长不下降子序列的长度,循环的时候我们只需要维护好d这个数组还有len就可以了。

      关键问题就是怎么维护?

      可以看出我们是要用logn的复杂度维护的。实际上利用了d数组的一个性质:单调性。(长度更长了,d[k]的值是不会减小的)

      考虑新进来一个元素a[i]:

      如果这个元素大于等于d[len],直接让d[len+1]=a[i],然后len++。这个很好理解,当前最长的长度变成了len+1,而且d数组也添加了一个元素。

      如果这个元素小于d[len]呢?说明它不能接在最后一个后面了。那我们就看一下它该接在谁后面。

        准确的说,并不是接在谁后面。而是替换掉谁。因为它接在前面的谁后面都是没有意义的,再接也超不过最长的len,所以是替换掉别人。那么替换掉谁呢?就是替换掉那个最该被它替换的那个。也就是在d数组中第一个大于它的。第一个意味着前面的都小于等于它。假设第一个大于它的是d[j],说明d[1..j-1]都小于等于它,那么它完全可以接上d[j-1]然后生成一个长度为j的不下降子序列,而且这个子序列比当前的d[j]这个子序列更有潜力(因为这个数比d[j]小)。所以就替换掉它就行了,也就是d[j]=a[i]。其实这个位置也是它唯一能够替换的位置(前面的替了不满足d[k]最小值的定义,后面替换了不满足不下降序列)

      至于第一个大于它的怎么找……STL upper_bound。每次复杂度logn。

      至此,我们就神奇的解决了这个问题。按照这个思路,如果需要求严格递增的子序列怎么办?

      仍然考虑新进来一个元素a[i]:

      如果这个元素大于d[len],直接让d[len+1]=a[i],然后len++。这个很好理解,当前最长的长度变成了len+1,而且d数组也添加了一个元素。

      如果这个元素小于等于d[len]呢?说明它不能接在最后一个后面了。那我们就看一下它该接在谁后面。

        同样的道理,只是换成lower_bound即可。每次复杂度logn。

    --------2018.4.14更新--------

    很久没看,没想到这篇文章有这么多人阅读了,感觉最长递增子序列这里讲的不是太清楚,因此补充一下。

    最长递增子序列和最长不下降子序列的不同之处在于,d数组的单调性更严格了:一定是单调递增的。

    可以用反证法来证明这一点:假设有d[i]=d[i+1],也就是长度为i+1的子序列最后一位最小是d[i+1],那假设这个子序列是a[1], a[2], ..., a[i+1],在这个子序列里面,a[i]<d[i+1]肯定成立,不然就不是最长递增子序列了。那也就是说a[i]<d[i]了,那a[1], a[2], ..., a[i]就构成了一个长度为i的子序列,而且最后一个数比d[i]小。那d[i]肯定就不符合定义了。

    那这个性质有什么意义呢?

    仍然考虑新进来一个元素a[i]:

      如果这个元素大于d[len],直接让d[len+1]=a[i],然后len++。这个很好理解,当前最长的长度变成了len+1,而且d数组也添加了一个元素。

      如果这个元素等于d[len],那么可以保证d[1..len-1]都是小于a[i]的(根据上面的证明),因此这个元素就没有什么意义了,直接忽略就好,因为它无法接在任何一个元素d后面产生一个更有优势的子序列。

      如果这个元素小于d[len],那么就在d数组中找到第一个大于等于它的元素(这个元素必然存在,至少d[len]就是),把这个元素替换成a[i]即可。

      实际上可以发现,小于等于的时候可以统一按照lower_bound替换的方式来处理。

    这样做肯定是对的,而邝斌的模板上实际上是求的最长不下降子序列,而不是最长上升子序列。不信可以测试一下"1 2 3 2 3 2"这个样例。切记,不要迷信权威,学会自己思考。

    ------------------------------------

    下面是最长不下降子序列的代码,注释里面说明了如何改成最长上升子序列。

    //最长不下降子序列nlogn  Song 
    
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    
    int a[40005];
    int d[40005];
    
    int main()
    {
        int n;
        scanf("%d",&n);
        for (int i=1;i<=n;i++) scanf("%d",&a[i]);
        if (n==0)  //0个元素特判一下 
        {
            printf("0
    ");
            return 0;
        }
        d[1]=a[1];  //初始化 
        int len=1;
        for (int i=2;i<=n;i++)
        {
            if (a[i]>=d[len]) d[++len]=a[i];  //如果可以接在len后面就接上,如果是最长上升子序列,这里变成> 
            else  //否则就找一个最该替换的替换掉 
            {
                int j=upper_bound(d+1,d+len+1,a[i])-d;  //找到第一个大于它的d的下标,如果是最长上升子序列,这里变成lower_bound 
                d[j]=a[i]; 
            }
        }
        printf("%d
    ",len);    
        return 0;
    }

      想了一晚上这个问题终于想通了。前面说的“最该替换的位置”实际上不是很精确,那个位置替换掉是它唯一能够替换的位置,之所以要替换,就是为了维护d这个数组,让它始终满足最初的定义。

      nlogn复杂度的最长上升子序列还有树状数组的写法,可以参考我的另一篇文章:https://www.cnblogs.com/acmsong/p/7231069.html

  • 相关阅读:
    【C++】资源管理
    【Shell脚本】逐行处理文本文件
    【算法题】rand5()产生rand7()
    【Shell脚本】字符串处理
    Apple iOS产品硬件参数. 不及格的程序员
    与iPhone的差距! 不及格的程序员
    iPhone游戏 Mr.Karoshi"过劳死"通关. 不及格的程序员
    XCode V4 发布了, 苹果的却是个变态. 不及格的程序员
    何时readonly 字段不是 readonly 的?结果出呼你想象!!! 不及格的程序员
    object file format unrecognized, invalid, or unsuitable Command 不及格的程序员
  • 原文地址:https://www.cnblogs.com/itlqs/p/5743114.html
Copyright © 2011-2022 走看看