您的位置:首页 > 其它

最长上升子序列nlog(n)

2015-12-30 12:09 363 查看
nlog(n)的方法从很久之前就看了,愣是没看懂,因为这个条件

现在,我们仔细考虑计算F[t]时的情况。假设有两个元素A[x]和A[y],满足 (F[]保存LIS长度)
(1)x < y < t 
(2)A[x] < A[y] < A[t] 
(3)F[x] = F[y] 

没看懂,当A[x]<A[y]时,怎么会F[x]=F[y]呢?

其实这只是传递一个思想而已,当两者的LIS都一样时取最小值效果更好。只要明白这一点就行了。

再根据那个思想,我们会得到一个启示:根据F[]的值进行分类。对于F[]的每一个取值k,我们只需要保留满足F[t] = k的所有A[t]中的最小值。设D[k]记录这个值,即D[k] = min{A[t]} (F[t] = k)。 

注意到D[]的两个特点: (这个特点很重要)
(1) D[k]的值是在整个计算过程中是单调不下降的。 
(2) D[]的值是有序的,即D[1] < D[2] < D[3] < ... < D
。 

利 用D[],我们可以得到另外一种计算最长上升子序列长度的方法。设当前已经求出的最长上升子序列长度为len。先判断A[t]与D[len]。若A [t]
> D[len],则将A[t]接在D[len]后将得到一个更长的上升子序列,len = len + 1, D[len] = A [t];否则,在D[1]..D[len]中,找到最大的j,满足D[j] < A[t]。令k = j + 1,则有A [t] <= D[k],将A[t]接在D[j]后将得到一个更长的上升子序列,更新D[k] = A[t]。最后,len即为所要求的最长上 升子序列的长度。 

在 上述算法中,若使用朴素的顺序查找在D[1]..D[len]查找,由于共有O(n)个元素需要计算,每次计算时的复杂度是O(n),则整个算法的 时间复杂度为O(n^2),与原来的算法相比没有任何进步。但是由于D[]的特点(2),我们在D[]中查找时,可以使用二分查找高效地完成,则整个算法 的时间复杂度下降为O(nlogn),有了非常显著的提高。需要注意的是,D[]在算法结束后记录的并不是一个符合题意的最长上升子序列!

例如:

 0123456
a[]1735948
c[]-11 3(7)(4)5    9(8)  
#include <iostream>
#include <algorithm>
#include <cstring>
#include <climits>
#include <cstdio>

using namespace std;
#define N 200001
int n,a
,dp
;
int find(int len,int n) //二分,返回值为x,则c[x]>n>c[i-1],返回比n大的最小值
{
int mid,l=1,r=len;
while(l<=r)
{
mid=(l+r)/2;
if(dp[mid]>n)
r=mid-1;
else if(dp[mid]<n)
l=mid+1;
else
return mid;
}
return l;
}
int main()
{
while(~scanf("%d",&n))
{
for(int i=0;i<n;i++)
cin>>a[i];
for(int i=0;i<=n+1;i++)
dp[i]=N;
dp[1]=a[0];
int max=1;
for(int i=1;i<n;i++)
{
int d=find(max,a[i]); //不知为什么max改为i+1就不能过了,但是在hdu上能过
dp[d]=a[i];
if(d>max)
max=d;
}
cout<<max<<endl;
}
return 0;
}

有个更剪短优化的代码,就是利用STL中的lower_bound
函数lower_bound()在first和last中的前闭后开区间进行二分查找,返回大于或等于val的第一个元素位置。如果所有元素都小于val,则返回last的位置



函数upper_bound()返回的在前闭后开区间查找的关键字的上界,如一个数组number序列1,2,2,4.upper_bound(2)后,返回的位置是3(下标)也就是4所在的位置,同样,如果插入元素大于数组中全部元素,返回的是last。(注意:此时数组下标越界!!)

返回查找元素的最后一个可安插位置,也就是“元素值>查找值”的第一个元素的位置
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAX=100100;
int num[MAX],top=0;
int main()
{
int n;
while(~scanf("%d",&n))
{
scanf("%d",&num[0]);
top=1;
for(int i=1;i!=n;i++)
{
scanf("%d",&num[i]);
int * p=lower_bound(num,num+top,num[i]);  //不是很懂
if(p-num==top) ++top;
*p=num[i];
}
printf("%d\n",top);
}

}




                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  dp