最长上升子序列,众所周知,是dp的经典问题。用简单的dp解决的复杂度是O(n方),用dp+二分的方法解决的复杂度是O(nlogn).
1、转移方程:
MaxLen (1) = 1
MaxLen (k) = Max { MaxLen (i):1<i < k 且 ai < ak且 k≠1 } + 1
2、
假定存在一个序列d[1...9]=2 1 5 3 6 4 8 9 7,可以看出LIS长度为5。现在开始一步一步的找出来最终的结果。
定义序列B,令i=1...9来逐个考察。此外用变量len来记录现在最长算到了多少了。
首先,把d[1]放到B里,即B[1]=d[1],就是说长度为1的LIS的最小末尾是2,这时len=1,然后把d[2]有序的放到B里,令B[1]=1(d[2]),就是说长度为1的LIS最小末尾是1,d[1]=2已经没有用了,这是len=1不变。接着d[3]=5,5>1,所以令B[1+1]=d[3]=5,就是说长度为2的LIS的最小末尾是5,此时B[1...2]=1,5,len=2。
再来,d[4]=3,它正好在1和5之间,当然放在1的位置上不合适,因为1<3,因此长度为2的最小末尾应该是3,淘汰掉5,这时候B[1...2]=1,3,len=2,继续d[5]=6,它在3后边,所以B[3]=4,B[1...3]=1,3,6,len=3。
第6个,d[6]=4,它加在3和6之间,所以我们把6换掉,这样B[1...3]=1,3,4,len=3。
第7个,d[7]=8,8>4,所以B[4]=8,B[1...4]=1,3,4,8,len=4。
第8个,d[8]=9,B[1...5]=1,3,4,8,9,len=5。
最后一个,d[9]=7,所以B[1...5]=1,3,4,7,9,len=5。
注意这里的B并不是LIS,而是对应长度的LIS的最小末尾。
现在可以发现B插入数据是有序的,而且进行替换而不需要移动,也就是说可以使用二分查找,时间复杂度就降下来了。
注意了,这种算法只能得到最终的数据个数,但是如果需要得到具体的内容就不行了。
摘自Lce_Crazy的博客
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
const int MAXN = 500000 + 100;
int a[MAXN];
int b[MAXN];
using namespace std;
int main()
{
int n,left,right,mid;
while(~scanf("%d",&n))
{
memset(b,0,sizeof(b));
int len = 1;
for(int i = 1;i <= n;i++)
{
scanf("%d",a[i]);
}
b[1] = a[1];
for(int i = 2;i <= n;i++)
{
left = 1;
right = len;
while(left < right)
{
mid = (left + right) / 2;
if(a[i] >= b[mid])
left = mid + 1;
else
right = mid;
}
if(b[right] > a[i])
{
b[right] = a[i];
}
else
b[++len] = a[i];
}
printf("%d\n",len);
}
return 0;
}