最长递增子序列(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 }
方法三:通过辅助数组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 }