最长上升子序列 题解

题目网址:https://www.luogu.com.cn/problem/B3637

题目描述

这是一个简单的动规板子题。

给出一个由 n(n≤5000)个不超过 10^6 的正整数组成的序列。请输出这个序列的最长上升子序列的长度。

最长上升子序列是指,从原序列中按顺序取出一些数字排在一起,这些数字是逐渐增大的。

输入格式

第一行,一个整数 n,表示序列长度。

第二行有 n 个整数,表示这个序列。

输出格式

一个整数表示答案。

输入输出样例

输入 #1

6

1 2 4 1 3 4

输出 #1

4

说明/提示

分别取出 1、2、3、4 即可。

看完题最先想到的是使用数组f[i]表示以第i个数字结尾的最长上升子序列,a[1]~a[i-1]中小于a[i]的f最大值,这里用max[f]表示,f[i]=max[f]+1.

实现也不难,使用双重循环枚举,时间复杂度为1+2+3+...+(n-1)=n^2/2,对于本题中n<=5000来说可以满分,但是如果数据范围再大一点,怎么做呢?

这个时候我们可以考虑使用f数组来存储上升子序列,再使用变量sum来存储子序列的长度,最后只需输出sum即可。

要想实现这一点也不难:

我们首先将f[1]初始化为a[1];

接着向后循环,对于a[i](1<i<=n)来说,有以下几种情况:

1、a[i]>f[sum](sum既为f的长度,也为f中最大数字的位置),此时需更新f的长度以及最高位;

2、a[i]<f[1](f[1]为f中最小数),此时需更新f最低位;

3、f[1]<=a[i]<=f[sum]且a[i]不等于f中任意一个数,此时由于f中所有数都位于a[i]前面,且对于a[i]之后的数,我们要使f中任意的上升子序列中最大数尽可能最小,所以我们需要执行的操作为找出一个数v,使f[v-1]<a[i]且f[v]>a[i],此时将f[v]赋值为a[i]。在执行完上述操作后,我们既保证不会混乱其他子序列的最高位,也使得此子序列中的最高位最小。

4、f[1]<=a[i]<=f[sum]且a[i]等于f中某一个数,此时更新与不更新没有差别,可以不做操作。

将上述操作转换为代码即为:

1、

if(a[i]<f[1]) f[1]=a[i];

2、

if(a[i]>f[sum])

{

    sum++;

    f[sum]=a[i];

}

3、对这操作,我们如果只是枚举f[1]~f[sum]我们同样会超时,这个时候,我们发现f数组中的数全是由小到大排列的,这个时候我们需要找到其中一个符合条件的数,可以使用二分法来实现:

定义l=1;r=sum;mid=l=(l+r)/2;

此时判断f[mid]与a[i]的关系:

如果f[mid]<a[i],可以确定mid不是要找的v,可以使l=mid+1;

如果f[mid]>a[i],无法确定mid是不是要找的v,但可以确定mid+1不是要找的v,可以使r=mid;

如此循环往复,即可使l=r,则此时l与r既为要找的v;

l=1;

r=sum;

while(l<r)

{

    mid=(l+r)/2;

    if(f[mid]<a[i]) l=mid+1;

    else if(f[mid]>a[i]) r=mid;

}

if(l==r) f[r]=a[i];

4、在循环时如果f[mid]=a[i],此时直接break跳出即可,添加一行判断语句即可。

l=1;

r=sum;

while(l<r)

{

    mid=(l+r)/2;

    if(f[mid]<a[i]) l=mid+1;

    else if(f[mid]>a[i]) r=mid;

    else break;

}

if(l==r) f[r]=a[i];

代码:

#include<bits/stdc++.h>

using namespace std;

int a[5005];//存储数组

int f[5005];//存储上升子序列

int main()

{

    int n,i,j,,sum=1,l,r,mid;//n:数列长度 i,j:循环变量 sum:最长的子序列长度以及f中最高位 l,r,mid:二分用变量

    cin>>n;

    for(i=1;i<=n;i++)

    {

        cin>>a[i];

        if(i==1) f[1]=a[1];//更新初始值

        else if(a[i]<f[1]) f[1]=a[i];//更新最小值

        else if(a[i]>f[sum])//更新最大值

        {

            sum++;//更新后长度加1

            f[sum]=a[i];

        }

        else

        {

            l=1;

            r=sum;

            while(l<r)

            {

                mid=(l+r)/2;

                if(f[mid]<a[i]) l=mid+1;//确定不是v的范围f[1]~f[mid]

                else if(f[mid]>a[i]) r=mid;//确定不是v的范围f[mid+1]~f[sum]

                else break;//f[mid]等于a[i]时直接跳出

            }

            if(l==r)//判断是否直接跳出,跳出则l<r

            {

                f[r]=a[i];//赋值使子序列最高位最小 

            }

        }

    }

    cout<<sum;//输出最长子序列长度(f更新到的最远位置) 

    return 0;

}

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值