最长上升子序列 LIS nlogn

给出一个 1 ~ n (n ≤ 10^5) 的排列 P 
求其最长上升子序列长度

Input

第一行一个正整数n,表示序列中整数个数; 
第二行是空格隔开的n个整数组成的序列。

Output

最长上升子序列的长度

题解

这里给出两种方法,先说经典版本的,设dp【i】表示以以 a【i】为结尾的LST的长度,n方的暴力很好想,显然我们在i之间找到一个最大的LST,且要保证a[j]<a[i],那么显然dp[i]=max(dp[i],dp[j]+1),那么这个dp显然就是在i之前找到一个以小于a[i]结尾元素,用来更新当前的a[i],我们可以直接用满足条件的最长LST来更新就可以了……

所以就用棵线段树来维护一下1到a[i]-1的dp数组的最大值就可以了。代码讲完一起贴。

然后是鬼畜版本的,当然主要是状态,要绕下弯,设dp[i]表示长度为i的LST,结尾元素的最小值,为什么会想到这个,因为显然结尾的值越小,转移更优,然后显然dp数组是单调的,那么就好办了,我们每次枚举一个序列的元素,去更新,更新当前可以更新的最大的长度,更新的条件就是元素x>dp[i],然后二分出最大的i就可以,也只要更新最大的i就可以了为什么就自己想想吧,还比较有思考价值……

经典版:

#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<algorithm>
#include<cstring>
const int MAXN=100010;
using namespace std;
struct tree{
    int l,r,ma;
}a[4*MAXN];
int dp[MAXN],n,ans=0,nn=0;
int lian[MAXN]; 

void cl(){
    memset(dp,0,sizeof(dp));
    memset(lian,0,sizeof(lian));
}

void build(int id,int l,int r){
    if(l==r){
        a[id].l=l;
        a[id].r=r;
        a[id].ma=0;
        return;
    }
    a[id].l=l;
    a[id].r=r;
    int mid=(l+r)/2;
    build(id*2,l,mid);
    build(id*2+1,mid+1,r);
    a[id].ma=max(a[id*2].ma,a[id*2+1].ma);
}

int kanxun(int id,int l,int r){
    int L=a[id].l,R=a[id].r,mid=(L+R)/2;
    if(l==L&&r==R){
        return a[id].ma;
    }
    if(r<=mid) return kanxun(id*2,l,r);
    if(l>mid) return kanxun(id*2+1,l,r);
    else return max(kanxun(id*2,l,mid),kanxun(id*2+1,mid+1,r));
}

void insert(int id,int aum,int x){
    int l=a[id].l,r=a[id].r,mid=(l+r)/2;
    if(l==r&&l==aum){
        a[id].ma=x;
        return;
    }
    if(aum<=mid) insert(id*2,aum,x);
    else insert(id*2+1,aum,x);
    a[id].ma=max(a[id*2].ma,a[id*2+1].ma);
}

int main(){
    cl();
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&lian[i]),nn=max(nn,lian[i]);
    dp[0]=0;
    build(1,1,nn);
    for(int i=1;i<=n;i++){
        int j;
        if(lian[i]==1) j=0;
        else j=kanxun(1,1,lian[i]-1);
        dp[i]=j+1;
        insert(1,lian[i],dp[i]);
    }
    for(int i=1;i<=n;i++) ans=max(ans,dp[i]);
    printf("%d",ans);
}  

鬼畜版:

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<stdlib.h>
#include<cstring>
using namespace std;
int dp[1000010];
int main(){
    memset(dp,0,sizeof(dp));
    int n,maxi=0,l,r,mid,ans=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        int x;
        scanf("%d",&x);
        l=1,r=maxi,ans=0;
        while(l<=r){
            mid=(l+r)/2;
            if(x>=dp[mid]) ans=mid,l=mid+1;
            else r=mid-1;
        }
        if(ans==maxi) dp[++maxi]=x;
        else dp[ans+1]=min(dp[ans+1],x);
    }
    printf("%d",maxi);
    return 0;
}
 
时间: 2024-10-26 09:09:25

最长上升子序列 LIS nlogn的相关文章

nlogn 求最长上升子序列 LIS

最近在做单调队列,发现了最长上升子序列O(nlogn)的求法也有利用单调队列的思想. 最长递增子序列问题:在一列数中寻找一些数,这些数满足:任意两个数a[i]和a[j],若i<j,必有a[i]<a[j],这样最长的子序列称为最长递增子序列. 设dp[i]表示以i为结尾的最长递增子序列的长度,则状态转移方程为: dp[i] = max{dp[j]+1}, 1<=j<i,a[j]<a[i]. 这样简单的复杂度为O(n^2),其实还有更好的方法. 考虑两个数a[x]和a[y],x&

最长上升子序列LIS解法(n^n &amp;&amp; nlogn)

最长递增子序列问题 在一列数中寻找一些数满足 任意两个数a[i]和a[j] 若i<j 必有a[i]<a[j] 这样最长的子序列称为最长递增子序列LIS LIS问题有两种常见的解法 一种时间复杂度n^n 一种时间复杂度nlogn 下面我们先来说一下n^n的算法 设dp[i]表示以i结尾的最长上升子序列的长度 把问题分解 分解成序列中每一项最为终点的最大上升子序列 从第二项开始依次判断 最后找出最大的一项就是答案 则状态转移方程为 dp[i] = max{dp[j]+1}, 1<=j<

最长递减子序列(nlogn)(个人模版)

最长递减子序列(nlogn): 1 int find(int n,int key) 2 { 3 int left=0; 4 int right=n; 5 while(left<=right) 6 { 7 int mid=(left+right)/2; 8 if(res[mid]>key) 9 { 10 left=mid+1; 11 } 12 else 13 { 14 right=mid-1; 15 } 16 } 17 return left; 18 } 19 20 int Lis(int a[

最长递增子序列 O(NlogN)算法

https://www.felix021.com/blog/read.php?entryid=1587&page=3&part=1 感谢作者! 标题:最长递增子序列 O(NlogN)算法 出处:Blog of Felix021 时间:Wed, 13 May 2009 04:15:10 +0000 作者:felix021 地址:https://www.felix021.com/blog/read.php?1587  内容: 今天回顾WOJ1398,发现了这个当时没有理解透彻的算法. 看了好久

poj1836——dp,最长上升子序列(lis)

poj1836——dp,最长上升子序列(lis) Alignment Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 13767   Accepted: 4450 Description In the army, a platoon is composed by n soldiers. During the morning inspection, the soldiers are aligned in a straight

最长上升子序列 O(nlogn)解法 (转)

转自 http://www.cppblog.com/mysileng/archive/2012/11/30/195841.html 最长递增子序列问题:在一列数中寻找一些数,这些数满足:任意两个数a[i]和a[j],若i<j,必有a[i]<a[j],这样最长的子序列称为最长递增子序列. 设dp[i]表示以i为结尾的最长递增子序列的长度,则状态转移方程为: dp[i] = max{dp[j]+1}, 1<=j<i,a[j]<a[i]. 这样简单的复杂度为O(n^2),其实还有更

最长上升子序列LIS模板

1 ///最长上升子序列LIS模板 2 int BinSerch(int l,int r,int cut) 3 { 4 while (l<=r) 5 { 6 int m=(l+r)>>1; 7 if (cut>d[m]&&cut<=d[m+1]) return m; 8 if (cut>d[m]) l=m+1; 9 else r=m-1; 10 } 11 return 0; 12 } 13 14 int LIS(int n) 15 { 16 int le

动态规划(DP),最长递增子序列(LIS)

题目链接:http://poj.org/problem?id=2533 解题报告: 状态转移方程: dp[i]表示以a[i]为结尾的LIS长度 状态转移方程: dp[0]=1; dp[i]=max(dp[k])+1,(k<i),(a[k]<a[i]) #include <stdio.h> #define MAX 1005 int a[MAX];///存数据 int dp[MAX];///dp[i]表示以a[i]为结尾的最长递增子序列(LIS)的长度 int main() { int

最长递增子序列 LIS 时间复杂度O(nlogn)的Java实现

关于最长递增子序列时间复杂度O(n^2)的实现方法在博客http://blog.csdn.net/iniegang/article/details/47379873(最长递增子序列 Java实现)中已经做了实现,但是这种方法时间复杂度太高,查阅相关资料后我发现有人提出的算法可以将时间复杂度降低为O(nlogn),这种算法的核心思想就是替换(二分法替换),以下为我对这中算法的理解: 假设随机生成的一个具有10个元素的数组arrayIn[1-10]如[2, 3, 3, 4, 7, 3, 1, 6,