Openjudge 1759:最长上升子序列

  • 题目
    • 描述
    • 输入
    • 输出
    • 输入输出样例
  • On2算法
    • 分析
      • 状态转移方程
      • 初始化
    • Ac代码
  • Onlogn算法
    • 分析
      • 初始化
      • 关于二分查找
    • Ac代码
    • 一个有助于更好地理解这个算法的调试

题目

总时间限制: 2000ms 内存限制: 65536kB

描述

一个数的序列bi,当b1 < b2 < … < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, …, aN),我们可以得到一些上升的子序列(ai1, ai2, …, aiK),这里1 <= i1 < i2 < … < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8).

你的任务,就是对于给定的序列,求出最长上升子序列的长度。

输入

输入的第一行是序列的长度N (1 <= N <= 1000)。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000。

输出

最长上升子序列的长度。

输入输出样例

样例输入
7
1 7 3 5 9 4 8
样例输出
4
来源
翻译自 Northeastern Europe 2002, Far-Eastern Subregion 的比赛试题


O(n^2)算法

分析

状态转移方程

动态规划(dp)的入门经典题目
很容易得出状态转移方程为:

f[i]=max{f[1],f[2],f[3]...f[j]}+1 (0

初始化

for(int i=1;i<=n;i++) f[i]=1;

一定要把f数组全部初始化成1,如果只初始化f[1]为1的话
当样例为类似于:
    5
    12 1 2 3 4 5
的时候,正解应该是5;而只初始化f[1]的话,答案会是3.

Ac代码

#include
#include
using namespace std;
int n,a[1005],f[1005],ans=-1;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        f[i]=1;
    }
    for(int i=2;i<=n;i++)
        for(int j=1;jif(a[j]1);
        }
    for(int i=1;i<=n;i++)
        ans=max(ans,f[i]);
    printf("%d",ans);
}

O(nlogn)算法

分析

变量说明:将读入的序列存放到a数组中,开一个数组d,d[i]表示长度为i的上升子序列的末尾元素。整数len指向数组d最后一个元素的位置,所以最后len就是最长上升子序列的长度。
具体操作:边读入x(读入序列中的元素)边判断:x比数组d的末尾元素(d[len])大的话,就把x接到数组d的后面——d[len+1]=x,len++;如果x<=d[len](这里说的是最长上升子序列),查找d数组中第一个大于等于x的元素的位置(果断二分)j,并执行操作:d[j]=x;

注:这里的 x 可以理解为a[i].

初始化

d[1]=a[1];
int len=1;

关于二分查找

1.如果是最长上升子序列的话,可以用stl里的lower_bound()函数

#include
int j=lower_bound(d+1,d+len+1,a[i])-d;
//lower_bound(begin,end,k)的作用是二分查找[begin,end)
//左闭右开的区间内第一个大于k的数的位置,并返回它的地址,
//所以要-d(数组名就是数组里第一个元素的地址)

2.如果是最长不下降子序列的话,就可以用stl里的upper_bound()函数,具体操作和上面的lower_bound()一致

关于stl里二分查找函数的详解见这里 ->戳

3.其他乱七八糟的东西..比如说最长不上升子序列 或者是 最长下降子序列,就手动二分查找吧!!

4.关于二分法的练习:
Openjudge 7940查找最接近的元素
以及此题的Ac代码:戳


Ac代码:

#include
#include
using namespace std;
int n,a[1001],d[1001];

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    int len=1;
    d[1]=a[1];
    for(int i=2;i<=n;i++)
    {
        if(a[i]>d[len])
        {
            d[++len]=a[i];
            continue;
        }
        int j=lower_bound(d+1,d+len+1,a[i])-d;
        d[j]=a[i];
    }
    printf("%d",len);
}

一个有助于更好地理解这个算法的调试:

c++里的调试实在是太辣鸡了!!于是附上边执行边输出执行过程的代码:

#include
#include
#include
using namespace std;
int n,a[1001],d[1001];

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    int len=1;
    d[1]=a[1];
    //printf("Step %d: add %d \n",len,d[len]);
    //没错,注释掉的这几句就是过程的输出
    for(int i=2;i<=n;i++)
    {
        if(a[i]>d[len])
        {
            d[++len]=a[i];
            //printf("Step %d: add %d \n",len,d[len]);
            //cout<<"Now the array is: "<"    ";
            //for(int k=1;k<=len;k++)
                //printf("%d ",d[k]);
            //cout<continue;
        }
        int j=lower_bound(d+1,d+len+1,a[i])-d;
        //int bbb=d[j];
        d[j]=a[i];
        //printf("\nchange %d->%d\n",bbb,d[j]);
        //cout<<"Now the array is: "<"    ";
        //for(int k=1;k<=len;k++)
            //printf("%d ",d[k]);
        //cout<<"\n\n";
    }
    printf("%d",len);
}

附上图片展示:
输入样例为8
389 207 155 300 299 170 158 65
(偷懒用了最长不上升子序列-luogu导弹拦截 的样例..)

Openjudge 1759:最长上升子序列_第1张图片

附:

关于NlogN算法的一篇特别好的讲解

以及luogu上一道关于最长不上升子序列的题目
P1020 导弹拦截 .

你可能感兴趣的:(动态规划,分治)