最长上升子序列nlogn算法

转自 https://blog.csdn.net/shuangde800/article/details/7474903

HDU 1950

最长上升子序列问题可以优化为nlogn的算法。

定义d[k]:长度为k的上升子序列的最末元素,若有多个长度为k的上升子序列,则记录最小的那个最末元素。
注意d中元素是单调递增的,下面要用到这个性质。
首先len = 1,d[1] = a[1],然后对a[i]:若a[i]>d[len],那么len++,d[len] = a[i];
否则,我们要从d[1]到d[len-1]中找到一个j,满足d[j-1]<a[i]<d[j],则根据D的定义,我们需要更新长度为j的上升子序列的最末元素(使之为最小的)即 d[j] = a[i];
最终答案就是len

利用d的单调性,在查找j的时候可以二分查找,从而时间复杂度为nlogn。

下面的代码是自己写的

#include<cmath>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define REP(i,a,b) for(int i=(a);i<=(b);++i)
#define MAX 40010
int num[MAX];
int dp[MAX];//dp[len]为长度为len的最长上升子序列的最小末尾
int main()
{
    int n;int p;cin>>n;
    while(n--){
        cin>>p;int len=1;
        REP(i,1,p)scanf("%d",&num[i]);
        dp[1]=num[1];
        REP(i,2,p){
            //cout<<dp[len]<<' '<<len<<' '<<num[i]<<endl;
            if(num[i]>dp[len]){
                len++;dp[len]=num[i];
            }
            else{
                int l=1,r=len;
                while(l<r){
                    if(num[i]>dp[(l+r)/2]&&num[i]<dp[(l+r)/2+1]){
                        dp[(l+r)/2+1]=num[i];break;
                    }
                    else if(num[i]>dp[(l+r)/2])l=(l+r)/2;
                    else if(num[i]<dp[(l+r)/2])r=(l+r)/2;
                    //cout<<num[i]<<' '<<l<<' '<<r<<endl;
                }
                if(len==1&&num[i]<dp[1])dp[1]=num[i];
                else if(l==r&&num[i]<dp[r]&&num[i]>dp[r-1])dp[r]=num[i];
            }
            //REP(j,1,len)cout<<dp[j]<<' ';cout<<endl<<endl;
        }
        cout<<len<<endl;
    }
    return 0;
}


猜你喜欢

转载自blog.csdn.net/yhjpku/article/details/80710878