ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

LIS(最长上升子序列)的三种经典求法

2019-06-09 09:51:21  阅读:251  来源: 互联网

标签:int long 求法 rg LIS 序列 include 我们


求最长上升子序列的三种经典方案:

给定一个长度为 \(N\) 的数列,求它数值单调递增的子序列长度最大为多少。即已知有数列 \(A\) , \(A=\{A_1,A_2....A_n\}\) ,求 \(A\) 的任意子序列 \(B\) ( \(B=\{A_{k_1},A_{k_2}....A_{k_p}\}\) ),使 \(B\) 满足 \(k_1<k_2<....<k_p\) 且 \(B=\{A_{k_1}<A_{k_2}<....<A_{k_p}\}\) 。求 \(p\) 的最大值。



\(solution\quad 1:\)

先说一种最普遍的方法,因为这道题很容易想到一种动态规划。我们要求最长上升子序列,而如果我们要将这个问题分阶段,那么我们肯定要知道我们当前阶段最后一个元素为多少,还有当前我们的序列有多长。我们可以用前者来充当第一维描述:设 \(F[i]\) 表示以 \(A[i]\) 为结尾的最长上身子序列的长度,而我们肯定只能从 \(i\) 前面的且末尾元素比 \(A[i]\) 小的状态转移过来:

\(F[i]=^{max}_{0\leq j<i,A[j]<a[i]}\{F[j]+1\}\)

初始值为 \(F[0]=0\) ,而答案可以是任何阶段中只要长度最长的那一个,所以我们边转移边统计答案。

复杂度: \(O(n^2)\)

\(code\quad 1:\)

#include<iostream>
#include<cstdio>
#define ll long long
#define rg register int
using namespace std;

int n,ans;
int a[10005];
int f[10005];

int main(){ cin>>n;
    for(rg i=1;i<=n;++i) cin>>a[i];
    for(rg i=1;i<=n;++i){
        for(rg j=1;j<i;++j)
            if(a[j]<a[i])f[i]=max(f[i],f[j]);
        ++f[i]; ans=max(ans,f[i]);
    }cout<<ans<<endl;
    return 0;
}

\(solution\quad 2:\)

然后我们先对上一种方法进行优化,我们将转移方程列一下:

\(F[i]=^{max}_{0\leq j<i,A[j]<a[i]}\{F[j]+1\}\)

我们发现大括号中的1与 \(j\) 似乎没有半毛钱关系,所以我们将它提取出来:

\(F[i]=^{max}_{0\leq j<i,A[j]<a[i]}\{F[j]\}+1\)

然后我们发现我们只需要将比 \(i\) 小的所有的符合 \(A[j]<A[i]\) 的 \(F[j]\) 的最大值求出来,但是这个条件 \(A[j]<A[i]\) 实在是太麻烦了,所以我们换一种方法:我们将 \(A\) 数组的每一个元素先记下他现在的标号,然后按照权值从小到大排序,接着我们按从大到小的顺序枚举 \(A\) 数组,这时我们需要从之前的标号比它小的状态转移过来,这个我们只需要建立一个与编号为下标维护长度的最大值的树桩数组即可,枚举 \(A\) 数组时按元素的序号找到它之前序号比他小的长度最大的状态更新,然后将它也加入树状数组中。 复杂度: \(O(nlog(n))\)

\(code\quad 2:\)

#include<iostream>
#include<cstdio>
#include<algorithm>
#define ll long long
#define rg register int
using namespace std;

int n;
int s[200005];

struct su{
    int v,id;
    inline bool operator <(su x){return v<x.v;}
}a[200005];

inline void add(int x,int y){
    for(;x<=n;x+=x&-x) s[x]=max(s[x],y);
}

inline int ask(int x){
    rg res=0;
    for(;x>=1;x-=x&-x) res=max(s[x],res);
    return res;
}

int main(){
    cin>>n;
    for(rg i=1;i<=n;++i)
        cin>>a[i].v,a[i].id=i;
    sort(a+1,a+n+1);
    for(rg i=1;i<=n;++i)
        if(i>1&&a[i].v!=a[i-1].v)// 这一步是去重,如果没有这个if就是求最长不下降子序列!
            add(a[i].id,ask(a[i].id)+1);
    cout<<ask(n)<<endl;
    return 0;
}

\(solution\quad 3:\)

这是最快的方法:贪心加二分查找

我们之前说过:我们肯定要知道我们当前阶段最后一个元素为多少,还有当前我们的序列有多长。前两种方法都是用前者做状态,我们为什么不可以用后做状态呢?:设 \(F[i]\) 表示长度为 \(i\) 的最长上升子序列的末尾元素的最小值,我们发现这个数组的权值一定单调不降(仔细想一想,这就是我们贪心的来由)。于是我们按顺序枚举数组 $ A$ ,每一次对 \(F[]\) 数组二分查找,找到小于 \(A[i]\) 的最大的 $F[j] $ ,并用它将 \(F[j+1]\)更新。

复杂度: \(O(nlogn)\)

\(code\quad 3:\)

#include<iostream>
#include<cstdio>
#include<algorithm>
#define ll long long
#define rg register int
using namespace std;

int n;
int a[200005];
int f[200005];

int main(){
    cin>>n;
    for(rg i=1;i<=n;++i) cin>>a[i];
    rg ans=1; f[1]=a[1];
    for(rg i=2;i<=n;++i){
        rg l=1,r=ans,mid;
        while(l<=r){
            mid=(l+r)>>1;
            if(a[i]<=f[mid])r=mid-1;
            else l=mid+1;
        }f[l]=a[i];
        if(l>ans)++ans;
    }cout<<ans<<endl;
    return 0;
}

标签:int,long,求法,rg,LIS,序列,include,我们
来源: https://www.cnblogs.com/812-xiao-wen/p/10992613.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有