最长上升子序列nlog(n)

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[n]。 

利 用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[]在算法结束后记录的并不是一个符合题意的最长上升子序列!

例如:

  0 1 2 3 4 5 6
a[] 1 7 3 5 9 4 8
c[] -1 1  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[N],dp[N];
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);
	}
	
}        



 

                
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值