常规的dp求LIS的时间复杂度为O(n2),对于n比较大的时候这是不能接受的。这时候我们就需要一个优秀的O(nlogn)的算法了。
这个算法是基于贪心的思想,具体来说就是开一个序列数组b,记录已经求得的“最长上升子序列”,当扫到一个元素大于序列b的最后一个元素时,就直接将扫到的元素加入序列b,否则就在b数组中二分查找第一个大于扫到的元素的元素,将其替换,因为这样序列的“潜力值”更大。
假设我们有一个序列{1,8,3,7,11,6,9,10}。
初始化b数组的第0个元素小于序列中的最小元素(比如0或-1,上次就被这个坑了)。
扫描到值1,1大于b中第0个元素,此时b为{1}
扫描到这8,8大于b末尾元素,此时b为{1,8}
扫描到3,3小于b末尾元素,二分查找替换掉8,此时b为{1,3}
扫描到7,直接加入,此时b为{1,3,7}
扫描到11,直接加入,此时b为{1,3,7,11}
扫描到6,小于末尾元素,二分查找替换掉7,此时b为{1,3,6,11}(此时b并不是按照a中的顺序来了)
扫描到9,小于末尾元素,二分查找替换掉11,此时b为{1,3,6,9}
扫描到10,直接加入,此时b为{1,3,6,9,10}
看完举的栗子,相信大家都多多少少明白,b中不一定记录的是a的单调上升序列,但是b数组的长度就是最长单调上升序列的长度了。
这里以POJ Longest Ordered Subsequence给出代码:
1 #include<algorithm> 2 #include<iostream> 3 #include<cstdio> 4 using namespace std; 5 6 int a[1001],b[1001]; 7 8 int n,len; 9 int main(){ 10 b[0]=-1; 11 scanf("%d",&n); 12 for(int i=1;i<=n;++i) scanf("%d",&a[i]); 13 for(int i=1;i<=n;++i){ 14 if(a[i]>b[len]){ 15 b[++len]=a[i]; 16 }else{ 17 int k=lower_bound(b+1,b+1+len,a[i])-b; 18 b[k]=a[i]; 19 } 20 } 21 cout<<len; 22 return 0; 23 }