• 最长上升子序列(LIS)


    (我先扯些没用的)

    我这个笨孩子

    学点东西好慢好慢的

    我还贪玩

    于是

    将自己陷入了一个超级超级超级差的境地

    我还傻乎乎的保有着天真的梦想(理想?)

    所以现在我要加倍的努力努力再努力了

    只能嘎油了

    唉....

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

    传送门

    总时间限制:
    2000ms
    内存限制:
    65536kB
    描述
    一个数的序列bi,当b1 < b2 < ... < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, ..., aN),我们可以得到一些上升的子序列(ai1, ai2, ..., aiK),这里1 <= i1 < i2 < ... < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8).

    你的任务,就是对于给定的序列,求出最长上升子序列的长度。
    输入
    输入的第一行是序列的长度N (1 <= N <= 1000)。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000。
    输出
    最长上升子序列的长度。
    样例输入
    7
    1 7 3 5 9 4 8
    样例输出
      4

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

    两种算法叭
    一种O(n2
    一种O(nlogn)
    一、O(n2
      一种很朴素的想法

    我们都知道,动态规划的一个特点就是当前解可以由上一个阶段的解推出, 由此,把我们要求的问题简化成一个更小的子问题。子问题具有相同的求解方式,只不过是规模小了而已。最长上升子序列就符合这一特性。我们要求n个数的最长上升子序列,可以求前n-1个数的最长上升子序列,再跟第n个数进行判断。求前n-1个数的最长上升子序列,可以通过求前n-2个数的最长上升子序列……直到求前1个数的最长上升子序列,此时LIS当然为1。

      让我们举个例子:求 2 7 1 5 6 4 3 8 9 的最长上升子序列。我们定义d(i) (i∈[1,n])来表示前i个数以A[i]结尾的最长上升子序列长度。

      前1个数 d(1)=1 子序列为2;

      前2个数 7前面有2小于7 d(2)=d(1)+1=2 子序列为2 7

      前3个数 在1前面没有比1更小的,1自身组成长度为1的子序列 d(3)=1 子序列为1

      前4个数 5前面有2小于5 d(4)=d(1)+1=2 子序列为2 5

      前5个数 6前面有2 5小于6 d(5)=d(4)+1=3 子序列为2 5 6

      前6个数 4前面有2小于4 d(6)=d(1)+1=2 子序列为2 4

      前7个数 3前面有2小于3 d(3)=d(1)+1=2 子序列为2 3

      前8个数 8前面有2 5 6小于8 d(8)=d(5)+1=4 子序列为2 5 6 8

      前9个数 9前面有2 5 6 8小于9 d(9)=d(8)+1=5 子序列为2 5 6 8 9

      d(i)=max{d(1),d(2),……,d(i)} 我们可以看出这9个数的LIS为d(9)=5

      总结一下,d(i)就是找以A[i]结尾的,在A[i]之前的最长上升子序列+1,当A[i]之前没有比A[i]更小的数时,d(i)=1。所有的d(i)里面最大的那个就是最长上升子序列。其实说的通俗点,就是每次都向前找比它小的和比它大的位置,将第一个比它大的替换掉,这样操作虽然LIS序列的具体数字可能会变,但是很明显LIS长度还是不变的,因为是替换掉了,并没有改变增加或者减少长度。但是我们通过这种方式是无法求出最长上升子序列具体是什么的,这点和最长公共子序列不同。

    状态设计:F[ i ]代表以A[ i ]结尾的LIS的长度

    状态转移:F[ i ]=max{ F[ j ]+1 ,F[ i ] }(1<=j< i,A[j]< A[i])

    边界处理:F[ i ]=1(1<=i<=n)

    时间复杂度:O(n^2)

    //O(n^2)的算法 
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    int main()
    {
        int n;
        scanf("%d",&n);
        int a[1005],d[1005];
        for(int i = 1;i <= n;i++)
        {
            scanf("%d",&a[i]);
            d[i] = 1;
        }
        for(int i = 1;i <= n;i++)
            for(int j = 1;j <= i;j++)
            {
                if(a[j] < a[i])
                    d[i] = max(d[j] + 1,d[i]); 
            }
        int ans = 0;
        for(int i = 1;i <= n;i++)
        {
            ans = max(ans,d[i]);
        }
        printf("%d",ans);
        return 0;
    }

    但是,显然,这太慢了,是过不去的

    所以,引出第二种做法

    二、O(nlogn)

    思路:

    新建一个low数组,low[i]表示长度为i的LIS结尾元素的最小值。对于一个上升子序列,显然其结尾元素越小,越有利于在后面接其他的元素,也就越可能变得更长。因此,我们只需要维护low数组,对于每一个a[i],如果a[i] > low[当前最长的LIS长度],就把a[i]接到当前最长的LIS后面,即low[++当前最长的LIS长度]=a[i]。
    那么,怎么维护low数组呢?
    对于每一个a[i],如果a[i]能接到LIS后面,就接上去;否则,就用a[i]取更新low数组。具体方法是,在low数组中找到第一个大于等于a[i]的元素low[j],用a[i]去更新low[j]。如果从头到尾扫一遍low数组的话,时间复杂度仍是O(n^2)。我们注意到low数组内部一定是单调不降的,所有我们可以二分low数组,找出第一个大于等于a[i]的元素。二分一次low数组的时间复杂度的O(lgn),所以总的时间复杂度是O(nlogn)。

      我们再举一个例子:有以下序列A[ ]=3 1 2 6 4 5 10 7,求LIS长度。

      我们定义一个B[i]来储存可能的排序序列,len为LIS长度。我们依次把A[i]有序地放进B[i]里。(为了方便,i的范围就从1~n表示第i个数)

      A[1]=3,把3放进B[1],此时B[1]=3,此时len=1,最小末尾是3

      A[2]=1,因为1比3小,所以可以把B[1]中的3替换为1,此时B[1]=1,此时len=1,最小末尾是1

      A[3]=2,2大于1,就把2放进B[2]=2,此时B[]={1,2},len=2

      同理,A[4]=6,把6放进B[3]=6,B[]={1,2,6},len=3

      A[5]=4,4在2和6之间,比6小,可以把B[3]替换为4,B[]={1,2,4},len=3

      A[6]=5,B[4]=5,B[]={1,2,4,5},len=4

      A[7]=10,B[5]=10,B[]={1,2,4,5,10},len=5

      A[8]=7,7在5和10之间,比10小,可以把B[5]替换为7,B[]={1,2,4,5,7},len=5

      最终我们得出LIS长度为5。但是,但是!!这里的1 2 4 5 7很明显并不是正确的最长上升子序列。是的,B序列并不表示最长上升子序列,它只表示相应最长子序列长度的排好序的最小序列。这有什么用呢?我们最后一步7替换10并没有增加最长子序列的长度,而这一步的意义,在于记录最小序列,代表了一种“最可能性”。假如后面还有两个数据8和9,那么B[6]将更新为8,B[7]将更新为9,len就变为7。读者可以自行体会它的作用。

      因为在B中插入的数据是有序的,不需要移动,只需要替换,所以可以用二分查找插入的位置,那么插入n个数的时间复杂度为〇(logn),这样我们会把这个求LIS长度的算法复杂度降为了〇(nlogn)。

    有了这个思路后

    再加上lower_bound函数

    就OK了

    #include<cstdio>
    #include<cstring> 
    #include<algorithm>
    using namespace std;
    int main()
    {
        int n,len = 0;
        int a[1005],b[1005]; 
        memset(b,0,sizeof(b));
        scanf("%d",&n);
        scanf("%d",&a[1]);
        b[1] = a[1];
        len = 1;
        for(int i = 2;i <= n;i++)
        {
            scanf("%d",&a[i]);
            if(a[i] > b[len])
                b[++len] = a[i];
            else
            {
                int j = lower_bound(b+1,b+len+1,a[i])-b;
                b[j] = a[i];
            }
        } 
        printf("%d",len);
        return 0;
    }
  • 相关阅读:
    安装IDM扩展
    Go_数组&切片
    Mycat概念&安装
    IDEA自定义主题
    完全卸载Oracle11g
    创建型模式——单例模式(Singleton)
    设计模式统计
    PHP解压带密码的zip文件
    Win推荐软件
    如何设置线程池的线程数?
  • 原文地址:https://www.cnblogs.com/darlingroot/p/10452881.html
Copyright © 2020-2023  润新知