最长上升子序列

【基础算法】最长上升子序列

时间限制: 1 Sec   内存限制: 64 MB

题目描述

给定一个整数序列A1A2A3….An。求它的一个递增子序列,使子序列的元素个数尽量多,元素不一定要求连续。

输入

第1行:1个整数n(1<=n<=5000),表示序列中元素的个数.

第2行-n+1行:每行1个整数x(-1000<=x<=1000),第i+1行表示序列中的第i个元素。

输出

第1行:1个整数k,表示最长上升子序列的长度。

第2行:k个用单个空格分开的整数,表示找到了最长上升子序列。如果有多个长度等于k的子序列,则输出最靠前的1个。

样例输入

8
1
3
2
4
3
5
4
6

样例输出

5
1 3 4 5 6
#--------------------------------------------------------------------------------#
最基础的动规题~就让我来详细地为小白们讲解一下吧

首先,讲解一下题目:
给你一个序列(就一串数),你可以理解为几个人。
这几个人身高各不相同(有的很矮(两三厘米什么的不要在意),有的很高(也就一两千厘米))。
现在你要在这几个人当中找出一些人做苦力,你需要使揪出来的人的身高从低到高(不改变顺序的情况下)排列。
当然,我们都希望人越多越好~就是让你算出最多有多少,并输出他们的编号。

再解释一下样例:
八个数:1 3 2 4 3 5 4 6
这几个人的身高……呵呵,要抓出几个来,使身高为从低到高排列,例如:
1 4 5 6
我们抓的就是①、④、⑥和⑧号犯人,这样就是从低到高了。
但是,这样是不是最多的呢,显然不是,最多的是:
1 3 4 5 6
就抓了①、②、④、⑥和⑧号犯人,他们就很荣幸地成为了苦力。

这里有一个细节——升高一样是不能在一起的,你可以理解为两个身高一样的在一起,我说我高,你说你高,于是便打起架来了……

当然,这只是针对于“最大上升子序列”,其他的也是一样,只是排列方式不同而已,如“最大不上升子序列”,就是抓出一堆人,使身高从高到低排列(可以一样(大概是他们不敢打架吧,不要在意细节))。

#--------------------------------------------------------------------------------#

好的题目解释完了,怎么做呢?
啊啊哈哈哈,当然是直接选就行了啊,专业名词——贪心
好的,很不幸,用贪心是不行的……

这里就要用一种新东西——动态规划(简称动规)

这种东西到底是什么呢,
总之,这是一种递推的思想,你需要将当前的状态(原谅我我找不出更好的词)用之前的状态表示出来,最后只需一个循环,你就可以通过f[1]得到f[n](通常是指最优解问题)。

好的,对于这道题,我们可以列出一个方程(且称这猥琐的东西为方程)

f[i]=max{f[1],f[2],...,f[i-1]}+1


这是什么意思呢?首先:

f[i]表示的就是第i个人(必须要有第i个人)之前最多可以抓多少个苦力,显然,f[1]=1,因为第1个人之前没有人比TA高……

max应该不陌生,就是取f[1],f[2],...,f[i-1]中最大的。

但是,如果你明白了一些,肯定会问:第i个人不一定比1-(i-1)人都要高啊?

所以,还是让j从1到i-1开始枚举f[j],但是,在选最大的时候要再加一个条件:a[j]<a[i],a是存身高(原始序列)的数组。

但是,a[j]<a[i]只是表示第j个人比第i个人小,不表示f[j]中的每一个人都比i要矮啊?不用担心,既然这个人已经进了f[j],TA就一定比j要矮,而j比i矮,所以,f[j]中包含的每一个人都会比i要矮。

于是就有了:if(f[j]>maxi&&a[j]<a[i]) maxi=f[j],这样就选出来了。

为什么最后还要加1呢?看看前面:“f[i]表示的就是第i个人(必须要有第i个人)”,所以,算上i,还要再加1。

关于求最多苦力就是这样了。


接下来还有,要输出苦力的编号:

在找maxi的时候,就把j记录下来,最后递归输出,这部分详情看代码。


有问题欢迎留言~

代码:

#include<cstdio>
#include<cstring>
int a[5005],n,maxans,flag=1;//a为原始数组,n是人数量,maxans表示最多的苦力在f中的哪个位置
int f[5005],ans[5005];//f是动规数组,ans是存最多苦力的编号的数组
void print(int x)
{
    if(ans[x]!=0)//直到父亲为0(没有了)
        print(ans[x]);
    if(flag) printf("%d",a[x]),flag=0;
    else printf(" %d",a[x]);//flag控制多余空格
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);//读入
    for(int i=1;i<=n;i++)
    {
        int maxi=0;//寻找f[1],f[2],...,f[i-1]中最大且符合条件的一个
        for(int j=1;j<i;j++)
            if(f[j]>maxi&&a[j]<a[i])
            {
                maxi=f[j];
                ans[i]=j;//可以理解为i的父亲(就是i是由j推来的)
            }
        f[i]=maxi+1;
        if(f[i]>f[maxans])//找最大的
            maxans=i;
    }
    printf("%d\n",f[maxans]);
    print(maxans);
}
                                                                                                                                                               By WZY


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值