最长递增子序列(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

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2020-04-06 22:25  DemonSlayer  阅读(316)  评论(0编辑  收藏  举报