LIS
五:LIS
概念
最长上升子序列(Longest Increasing Subsequence,LIS),在计算机科学上是指一个序列中最长的单调递增的子序列。比如一个序列31 2 6 3 8,他的最长上升子序列是1 2 6 8或者1 2 3 8;可以不是连续的元素。而需要注意的是子串的定义是连续元素。
关于LIS的朴素算法时间复杂度达到o(n^2),在做题中并不适用,重点分析另一种时间复杂度为o(n log n)的算法。
问题描述
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能超过前一发的高度.某天,雷达捕捉到敌国的导弹来袭.由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹.
怎么办呢?多搞几套系统呗!你说说倒蛮容易,成本呢?成本是个大问题啊.所以俺就到这里来求救了,请帮助计算一下最少需要多少套拦截系统。
基本要求
输入
输入若干组数据,每组数据包括:导弹总个数(正整数),导弹依此飞来的高度(雷达给出的高度数据是不大于30000的正整数,用空格分隔)
输出
对应每组数据输出拦截所有导弹最少要配备多少套这种导弹拦截系统.。
测试数据
输入
8
389 207 155 300 299 170 158 65
输出
2
算法思想
应该求最长不降子序列。这样的长度才是最少需要的套数,因为这个序列中的任何两个导弹都不能共用一个拦截系统,而且其余的导弹都能和这个最长序列中的某个导弹分为同一组。
所以此题就转变为求一个序列的最大上升子序列的长度。
实现过程
定义已知序列数组为dp[];dp[1…8]=389,207,155,300,299,170,158,65
我们定义一个序列B,然后令 i = 1 to 8
逐个考察这个序列。
此外,我们用一个变量Len来记录现在最长算到多少了
1)首先,把d[1]有序地放到B里,令B[1] = 389,就是说当只有1一个数字389的时候,长度为1的LIS的最小末尾是389。这时Len=1。
2)然后,把d[2]有序地放到B里,d[2]=207<B[1]=389,所以令B[1] = 207,就是说长度为1的LIS的最小末尾是207,d[1]=389已经没用了。这时Len=1
3)接着,d[3] = 155<B[1]=207,所以令B[1]=d[3]=155,就是说长度为1的LIS的最小末尾是155,这时候B[1] = 155,Len=1
4)再来,d[4] = 300>B[1]=155,所以B[1+1]=B[2]=300,长度为2的LIS最小末尾是300,这时候B[1..2] = 155, 300,Len = 2
5)继续,d[5] = 299,d[5]>B[1]&&d[5]<B[2]。这时令B[2]=d[5]=299,用d[5]替换掉B[2],长度为2的LIS的最小末尾是299,B[1…2]=155,299,。Len=2。
6)第6个, d[6] = 170,和上一个一样B[2]=170,长度为2的LIS最小末尾是170,B[1…2]=155,170。Len=2。
7)第7个, d[7] =158,同样B[2]=158,B[1..2]=155,158。Len=2。
8)最后一个, d[8] = 65<B[1],所以令B[1]=65,这时B[1..2]=65,158,Len=2。
于是我们知道了LIS的长度为2。
注意B中存放的并不是LIS序列,而是存储的对应长度LIS的最小末尾。有了这个末尾,我们就可以一个一个地插入数据。
在B中插入数据是有序的,而且是进行替换而不需要挪动——也就是说,我们可以使用二分查找,将每一个数字的插入时间优化到O(logN)~~~~~于是算法的时间复杂度就降低到了O(NlogN)。
代码实现
1 #include<stdio.h> 2 3 #include<iostream> 4 5 #include<algorithm> 6 7 using namespace std; 8 9 int B[100]; 10 11 int dp[100]; 12 13 int len; 14 15 int main() 16 17 { 18 19 int n; 20 21 scanf("%d",&n); 22 23 for(int i=1;i<=n;i++) 24 25 scanf("%d",&dp[i]); 26 27 B[1]=dp[1]; 28 29 len=1; 30 31 for(int i=2;i<=n;i++) 32 33 { 34 35 if(dp[i]>=B[len]) 36 37 B[++len]=dp[i]; 38 39 else 40 41 { 42 43 *lower_bound(B+1,B+1+len,dp[i])=dp[i];//调用库函数,进行二分查找第一个>=dp[i]的元素位置,返回值是指针,并替换。 44 45 } 46 47 } 48 49 printf("%d\n",len); 50 51 return 0; 52 53 }
运行截图
个人总结
切记,B[]中存放的并不是最长上升子序列,但他含有的元素个数等于最长上升子序列的长度,在代码实现中二分查找我使用了STL库函数,返回结果是指针,还可以这样写
int *pos=lower_bound(B+1,B+1+len,dp[i]); *pos=dp[i];效果是一样的。