• 最长上升/下降子序列


    描述

    一个数的序列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

    一、确定状态


    我们让dp[k] 代表从 1->k 的最长上升子序列的长度。 状态有N个

    二、确定转移方程

    让我们举个例子:求 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)里面最大的那个就是最长上升子序列

    想要求得最长上升子序列的长度,来源只有一种——前一个最长上升子序列的长度+1 ,据此就得到了状态转移方程 。

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

    Code:

     1 #include <iostream>
     2 #include <stdio.h>
     3 #include <algorithm>
     4 #include <stdlib.h>
     5 #include <string>
     6 #include <string.h>
     7 #include <set>
     8 #include <queue>
     9 #include <math.h>
    10 #include <stdbool.h>
    11 
    12 #define ll long long
    13 #define inf 0x3f3f3f3f
    14 using namespace std;
    15 const int MAXN = 1005;
    16 
    17 int dp[MAXN];
    18 int a[MAXN];
    19 int N;
    20 
    21 
    22 int main()
    23 {
    24     scanf("%d",&N);
    25     for (int i=1;i<=N;i++)
    26         cin >> a[i];
    27     for(int i=1;i<=N;i++)
    28         dp[i] = 1;
    29     for (int i=2;i<=N;i++)
    30     {
    31         for (int j=1;j<i;j++)
    32         {
    33             if (a[j]<a[i])
    34                 dp[i]=max(dp[j]+1,dp[i]);
    35         }
    36     }
    37     sort(dp+1,dp+1+N);
    38     cout << dp[N] << endl;
    39     return 0;
    40 
    41 }

    那么如果我们想输出这个最长上升子序列呢?

     1 #include <iostream>
     2 #include <stdio.h>
     3 #include <algorithm>
     4 #include <stdlib.h>
     5 #include <string>
     6 #include <string.h>
     7 #include <set>
     8 #include <queue>
     9 #include <math.h>
    10 #include <stdbool.h>
    11 
    12 #define ll long long
    13 #define inf 0x3f3f3f3f
    14 using namespace std;
    15 const int MAXN = 1005;
    16 
    17 int dp[MAXN];
    18 int a[MAXN];
    19 int N;
    20 
    21 
    22 int main()
    23 {
    24     freopen("../in.txt","r",stdin);
    25     int pre[MAXN];
    26     memset(pre,0, sizeof(pre));
    27     scanf("%d",&N);
    28     for (int i=1;i<=N;i++)
    29         cin >> a[i];
    30     for(int i=1;i<=N;i++)
    31         dp[i] = 1;
    32     for (int i=1;i<=N;i++)
    33     {
    34         for (int j=1;j<i;j++)
    35         {
    36             if (a[j]<a[i])
    37             {
    38                 dp[i] = max(dp[j] + 1, dp[i]);
    39             }
    40         }
    41     }
    42     int maxn = 0;
    43     for (int i=1;i<=N;i++)
    44     {
    45         if (dp[i]>maxn)
    46         {
    47             maxn = dp[i];
    48         }
    49     }
    50     int ans[100];
    51     for (int i=1;i<=maxn;i++)
    52     {
    53         for (int j=1;j<=N;j++)
    54         {
    55             if (dp[j] == i)
    56                 ans[i] = a[j];
    57         }
    58     }
    59     for (int i=1;i<=maxn;i++)
    60         printf("%d ",ans[i]);
    61     return 0;
    62 
    63 }

    优化算法:贪心+二分

     

    新建一个数组b,用来放置一个数列。当某一个数比它末尾的元素大时,把它插在末尾,表示最长上升子序列的长度增加了1。要不就看看它能否替换这个序列中比它小的数。替换一个更小的数,那么这个数列就更加具有“潜力”,它允许部分原来比末尾数子大的数加入。这个替换的过程可以理解成,我们尝试把序列中的某个靠前的较大的数字换成了现在靠后的较小的数。比如在1,5,10,6,7,8中,在i=4时,1,5,10数列改成1,6,10可以理解成一个1,6,……(期待新数连接)的数列。在i=5时,7替换了10,数列成了1,6,7;相当于7连接了上去。这样后,在i=6,8才能自然地接上,数列变成1,5,6,7,8,增加了最长上升子序列的长度。

    这样就做到了O(nlogn)的时间复杂度。

    Code:

     1 #include <iostream>
     2 #include <stdio.h>
     3 #include <algorithm>
     4 #include <stdlib.h>
     5 #include <string>
     6 #include <string.h>
     7 #include <set>
     8 #include <queue>
     9 #include <math.h>
    10 #include <stdbool.h>
    11 
    12 #define ll long long
    13 #define inf 0x3f3f3f3f
    14 using namespace std;
    15 const int MAXN = 1005;
    16 
    17 int dp[MAXN];
    18 int a[MAXN];
    19 int N;
    20 
    21 
    22 int main()
    23 {
    24     scanf("%d",&N);
    25     for (int i=1;i<=N;i++)
    26         cin >> a[i];
    27     memset(dp,inf, sizeof(dp));
    28     dp[1] = a[1];
    29     int len = 1;
    30     int pos;
    31     for (int i=1;i<=N;i++)
    32     {
    33         if (a[i]<=dp[len])
    34         {
    35             pos=lower_bound(dp+1,dp+len+1,a[i])-dp;
    36             dp[pos] = a[i];
    37         }
    38         else
    39         {
    40             len++;
    41             dp[len] = a[i];
    42         }
    43     }
    44     cout << len << endl;
    45     return 0;
    46 
    47 }

    求最长下降子序列:

    朴素的方法就没必要再说了。这里就说下 nlogn 的方法

    如果当前的 a[i] < dp[len]   那么直接给它加在这个序列的后面加好了

    如果当前的 a[i] >= dp[len]  那么就去找 dp 维护的这个序列里面第一个小于等于 a[i] 的位置,然后把它给替换了

    Code:

    int find(int k)
    {
        int l = 1,r = len,ans = 1;
        while(l<=r)
        {
            int mid = (l + r) >> 1;
            if(dp[mid] <= k)
            {
                ans = mid;
                r = mid - 1;
            }
            else
            {
                l = mid + 1;
            }
        }
        return ans;
    }
    
    int main() {
        len = 1;
        dp[len] = p[1].y;
        int pos;
        for (int i=1;i <= n;i++)
        {
            if (p[i].y >= dp[len])
            {
                pos = find(p[i].y);
                dp[pos] = p[i].y;
                ans[p[i].id] = pos;
            }
            else
            {
                len++;
                dp[len] = p[i].y;
                ans[p[i].id] = len;
            }
        }
    }

    模版:

     关于二分的部分:

    这里再讲一个最长上升子序列的变种题目:

    求最长上升子序列的个数

    这一个算法思想上和求最长子序列是差不多的,唯一难的地方在于如何去记录不同的序列

    对于最长子序列的个数,需要我们在处理dp的时候来记录,我们设count[i]为以第i个数结尾的最长序列的个数,与dp同理,count初值也都是1;


    状态转移的时候,如果dp更新了,也就是说(dp[j]+1>dp[i])说明这个长度的序列是新出现的,我们需要将count[i]设置为count[j],因为新序列中,最新的数提供了序列的尾巴,数量是由前面积累的(或者说转移) 

    举例序列[1 1 3 7]我们易得数字3对应的dp=2,count=2,因为可以构成两个[1 3]那么我们操作到数字7的时候,发现接在3后面最长,就可以转移count来作为初始数量;

    dp[j]+1==dp[i]的时候,如同样例,操作7的时候,我们最先发现了可以接在5后面,最长序列[1 3 5 7],然后发现可以接在4后面,[1 3 4 7],长度也是4,这时候就同样需要转移count,加上去 count[i]+=count[j]

    最后我们需要遍历dp,找到dp[i]=我们记录的最大值的时候,累加我们得到的count[i],即为所求结果,时间复杂度是O(n^2)

     1 #include <iostream>
     2 #include <stdio.h>
     3 #include <algorithm>
     4 #include <stdlib.h>
     5 #include <string>
     6 #include <string.h>
     7 #include <set>
     8 #include <queue>
     9 #include <math.h>
    10 #include <stdbool.h>
    11 
    12 #define ll long long
    13 #define inf 0x3f3f3f3f
    14 using namespace std;
    15 const int MAXN = 1005;
    16 
    17 int dp[MAXN];
    18 int a[MAXN];
    19 int cnt[MAXN];
    20 int N;
    21 
    22 
    23 int main()
    24 {
    25     scanf("%d",&N);
    26     for (int i=1;i<=N;i++)
    27         cin >> a[i];
    28     for (int i=1;i<=N;i++)
    29     {
    30         dp[i] = 1;
    31         cnt[i] = 1;
    32     }
    33     int mxn = 0;
    34     for (int i=1;i<=N;i++)
    35     {
    36         for (int j=1;j<i;j++)
    37         {
    38             if (a[j] < a[i])
    39             {
    40                 if (dp[j]+1 > dp[i])
    41                 {
    42                     dp[i] = dp[j] + 1;
    43                     cnt[i] = cnt[j];
    44                 }
    45                 else if (dp[j] + 1 == dp[i])
    46                 {
    47                     cnt[i] += cnt[j];
    48                 }
    49             }
    50         }
    51         mxn = max(mxn,dp[i]);
    52     }
    53     cout << mxn << endl;
    54     int res = 0;
    55     for (int i=1;i<=N;i++)
    56     {
    57         if (dp[i] == mxn)
    58         {
    59             res+= cnt[i];
    60         }
    61     }
    62     cout << res << endl;
    63     return 0;
    64 }
  • 相关阅读:
    玩裸机s3c2440资料集合
    windows7与虚拟机fedora 9.0文件共享
    仪表运算放大器INA333
    sql语句中的 case.. when...then ...else 用法
    JS判断RadioButtonList是否有选中项
    SQL SERVER 2005 同步复制技术
    软件安全性标准
    div+css网页标准版式布局
    ComboBox和SelectedIndexChanged(转)
    VC2005从开发MFC ActiveX ocx控件到发布到.net网站的全部过程
  • 原文地址:https://www.cnblogs.com/-Ackerman/p/11226596.html
Copyright © 2020-2023  润新知