• 最长递增子序列(Longest Increasing Subsequence,LIS)


      内容参考书籍《算法竞赛入门到进阶》

      LIS问题:给定一个长度为N的数组,找出一个最长的单调递增子序列。例如5,6,7,4,2,8,3,它的单调递增子序列为5,6,7,8,长度为4。

      下面通过一道经典例题来讲解 hdu 1257:http://acm.hdu.edu.cn/showproblem.php?pid=1257

    题意:该拦截系统的第一发炮弹能够达到任意高度,但是以后的每发炮弹不能超过前一发的高度。

    一:贪心法:假设发射了很多个高度无穷大的导弹,在读入一个炮弹时,就将一个导弹的高度下降来拦截这个炮弹,之后的每一个炮弹就由能拦截它的最低的那个导弹来拦截。最后统计用于拦截的导弹个数,也就是最少需要的拦截系统的套数。

    二:DP:换个说法,题目是让统计一个序列中的单调递减子序列最少有多少个。而我们刚刚提到了LIS是指求最长的单调递增子序列,假设我们已经掌握了LIS算法,那是不是说我们把序列反过来对这个反序列求一个LIS,然后去掉这个LIS再在剩下的数里面再求一个LIS直到序列为空,最后统计一下LIS的个数呢?实际上,不用这样麻烦,直接求该序列的LIS即可。

      证明如下:

      1.从第一个数开始,找一个最长的递减子序列,即第一个拦截子系统X,在样例中是{389,300,299,170,158,65},去掉后还剩下{207,155};

      2.在剩下的序列中再找一个最长递减子序列,即第二个拦截子系统Y,是{207,155}。

    不难发现,在Y中,至少有一个数a大于X中的某个数,否则a比X的所有数都小,应该在X中。所以我们从每一个拦截子系统中拿出一个数,是可以构成一个递增子序列的。拦截系统的数量等于这个递增子序列的长度,如果这个递增子序列不是最长,那么就可以从某个系统中拿出两个数,而拦截系统不是递增的,这就矛盾了。故此题直接求LIS即可。

      下面讲解求LIS。

      方法一:根据前面讲的LCS,我们对原序列排序得到{2,3,4,5,6,7,8},则该序列与原序列的LCS即是我们要求的LIS,复杂度为O(n2)

      方法二:直接DP。定义状态dp[i],表示以第i个数为结尾的最长递增子序列的长度,那么:dp[i]=max{0,dp[j]}+1,最后答案为max{dp(i)}。方法二的复杂度也是O(n2)

    下面给出方法二代码:

     1 #include <bits/stdc++.h>
     2 using namespace std;
     3 const int MAXN = 10000;
     4 int n,high[MAXN];
     5 int LIS()
     6 {
     7     int ans = 1;
     8     int dp[MAXN];
     9     dp[1] = 1;
    10     for (int i = 2; i <= n; ++i)
    11     {
    12         int max = 0;
    13         for (int j = 1; j < i; ++j)
    14         {
    15             if (dp[j] > max && high[j] < high[i]) max = dp[j];
    16         }
    17         dp[i] = max + 1;
    18         if (dp[i] > ans) ans = dp[i];
    19     }
    20     return ans;
    21 }
    22 int main(int argc, char const *argv[])
    23 {
    24     while(cin>>n)
    25     {
    26         for (int i = 1; i <= n; ++i)
    27         {
    28             cin>>high[i];
    29         }
    30         cout<<LIS()<<endl;
    31     }
    32     return 0;
    33 }
    hdu1257

      方法三:通过辅助数组d[]统计最长递增子序列的长度。复杂度O(nlog2n)。具体操作:逐个处理high[]中的数字,例如处理到high[k],如果high[k]比d[]末尾的数字更大,就加到d[]的后面;如果小,就替换d[]中第一个比它大的数字。

      下面以high[]={4,8,9,5,6,7}为例

    i high[] d[] len 说明
    1 4,8,9,5,6,7 4 1 初始化d[1]=high[1]
    2 4,8,9,5,6,7 4,8 2 high[2]>d[1],加到d[]的后面
    3 4,8,9,5,6,7 4,8,9 3 同上
    4 4,8,9,5,6,7 4,5,9 3 5比9小,用5替换第一个比5大的数即8
    5 4,8,9,5,6,7 4,5,6 3 6比9小,同上
    6 4,8,9,5,6,7 4,5,6,7 4 7比6大,加后面

     

     

     

     

    结束后len=4,即为LIS的长度。

    下面是代码:

     1 #include <bits/stdc++.h>
     2 using namespace std;
     3 const int MAXN = 10000;
     4 int n,high[MAXN];
     5 int LIS()
     6 {
     7     int len = 1;
     8     int d[MAXN];
     9     d[1] = high[1];
    10     for (int i = 2; i <= n; ++i)
    11     {
    12         if (high[i] > d[len]) d[++len] = high[i];
    13         else{
    14             int j = lower_bound(d+1,d+len+1,high[i])-d;
    15             d[j] = high[i];
    16         }
    17     }
    18     return len;
    19 }
    20 int main(int argc, char const *argv[])
    21 {
    22     while(cin>>n)
    23     {
    24         for (int i = 1; i <= n; ++i)
    25         {
    26             cin>>high[i];
    27         }
    28         cout<<LIS()<<endl;
    29     }
    30     return 0;
    31 }
    非dp

     

     

     

     

     

     

     

     

     

     

     

     

     

  • 相关阅读:
    基于Freescale的主流芯片HCS08
    BizTalk Server 2010 映射器(Mapper) [ 下篇 ]
    BizTalk Server 2010 使用 WCF Service [ 中篇 ]
    Ext JS 4 Beta 1发布了
    Step by Step WebMatrix网站开发之一:Webmatrix安装
    REST WebService与SOAP WebService的比较
    BizTalk Server 2010 使用 WCF Service [ 上篇 ]
    BizTalk Server 2010 映射器(Mapper) [ 中篇 ]
    BizTalk Server 2010 映射器(Mapper) [ 上篇 ]
    ExtJS 4 Beta 2预览:Ext.Brew包
  • 原文地址:https://www.cnblogs.com/125418a/p/12616739.html
Copyright © 2020-2023  润新知