2112: 聪明的美食家(最长不降子序列)

Description

如果有人认为吃东西只需要嘴巴,那就错了。
都知道舌头有这么一个特性,“由简入奢易,由奢如简难”(据好事者考究,此规律也适合许多其他情况)。具体而言,如果是甜食,当你吃的食物不如前面刚吃过的东西甜,就很不爽了。
大宝是一个聪明的美食家,当然深谙此道。一次他来到某小吃一条街,准备从街的一头吃到另一头。为了吃得爽,他大费周章,得到了各种食物的“美味度”。他拒绝不爽的经历,不走回头路而且还要爽歪歪(爽的次数尽量多)。

Input

  两行数据。
第一行为一个整数n,表示小吃街上小吃的数量
第二行为n个整数,分别表示n种食物的“美味度”

Output

一个整数,表示吃得爽的次数

Sample Input

10 3 18 7 14 10 12 23 41 16 24

Sample Output

6

HINT

美味度为0到10000的整数

n<200000

分析:

 完完全全一个最长不降子序列的模板题啊!找了一个模板,改了下数据范围,交上去就ac了,Σ(⊙▽⊙"a

最近中医药的题的难度是递增的,每次都有新的算法出现,而且是最简单的算法题的那种哈哈,那么这道题我的重点在于理解模板,理解算法。而且我又学会了一种除sort之外的stl。。。。upper-bound和lower-bound

 对于upper_bound来说,返回的是被查序列中第一个大于查找值的指针,也就是返回指向被查值>查找值的最小指针,lower_bound则是返回的是被查序列中第一个大于等于查找值的指针,也就是返回指向被查值>=查找值的最小指针。

最长不降子序列详解:(两种做法,n^2和logn)

O(n²)解法,DP

我们设每一个数为阶段,因为没有后效性,可以用DP

设F[i]表示选到了第i个数,1~i中最长不下降子序列的长度是F[i]

怎么转移?我们只要从j=1~i-1枚举一个数,因为当前F[i]为必选,所以我们假设枚举的这个数是上一个最长不下降子序列的数。

当然,要满足a[i]≥a[j]

很明显,在可以选的情况下,越大越好

所以F[i]=max(F[j]) (i=1~n,j=1~i-1)

<span style="font-size:14px;">for i:=1 to n do
                f[i]:=1;//一开始,F[i]=1,因为他们必选自己
 
        for i:=2 to n do//第一个必定为1,所以从第二个开始寻找
        begin
                max:=0;
                for j:=1 to i-1 do
                        if (a[i]>=a[j])and(f[j]>max) then//在前面寻找一个比他小(或者等于)的,一个最大的F[i]
                                max:=f[j];
                f[i]:=max+f[i];
        end;</span>

2.解决的问题:给定一个序列,求最长不下降子序列的长度(nlogn的算法没法求出具体的序列是什么)

  定义:a[1..n]为原始序列,d[k]表示长度为k的不下降子序列末尾元素的最小值,len表示当前已知的最长子序列的长度。

  初始化:d[1]=a[1]; len=1; (0个元素的时候特判一下)

  考虑新进来一个元素a[i]:

  如果这个元素大于等于d[len],直接让d[len+1]=a[i],然后len++。这个很好理解,当前最长的长度变成了len+1,而且d数组也添加了一个元素。

  如果这个元素小于d[len]呢?说明它不能接在最后一个后面了。那我们就看一下它该接在谁后面。

    准确的说,并不是接在谁后面。而是替换掉谁。因为它接在前面的谁后面都是没有意义的,再接也超不过最长的len,所以是替换掉别人。那么替换掉谁呢?就是替换掉那个最该被它替换的那个。也就是在d数组中第一个大于它的。第一个意味着前面的都小于等于它。假设第一个大于它的是d[j],说明d[1..j-1]都小于等于它,那么它完全可以接上d[j-1]然后生成一个长度为j的不下降子序列,而且这个子序列比当前的d[j]这个子序列更有潜力(因为这个数比d[j]小)。所以就替换掉它就行了,也就是d[j]=a[i]。其实这个位置也是它唯一能够替换的位置(前面的替了不满足d[k]最小值的定义,后面替换了不满足不下降序列)

  至于第一个大于它的怎么找……STL upper_bound。每次复杂度logn。

后来我发现最长不降子序列是可以输出的!!!

总有不服输的博主(你说不能输出就不能输出啊)啊摔!

为什么这个方法是对的呢?倒序查找保证了两个条件:

    - 如果 c 中有多个相同的数,后面的一定是最新更新的;

    - 在保证一条件的前提下,倒序找,后面的数一定可以接到前面数的后面。”

具体实现:

//From - Milky Way

#include <cstdio>
#include <algorithm>
#include <stack>
using namespace std;

int d[100], c[100], a[100], len = 1;

int main() {
    int n; scanf("%d", &n);
    for (int i = 1; i <= n; ++ i) {
        scanf("%d", &a[i]);
    }

    d[1] = a[1], c[1] = 1;
    for (int i = 2; i <= n; ++ i) {
        if (d[len] <= a[i]) {
            d[++ len] = a[i], c[i] = len;
        } else {
            int j = upper_bound(d + 1, d + len + 1, a[i]) - d;
            d[j] = a[i], c[i] = j;
        }
    }

    stack<int> sta;
    for (int i = n, j = len; i >= 1; -- i) {
        if (c[i] == j) {
            sta.push(a[i]); --j;
        }
        if (j == 0) break;
    }

    printf("%d\n", len);
    while (!sta.empty()) {
        printf("%d ", sta.top());
        sta.pop();
    }

    return 0;
}

最长不下降之输出子序列 - NlogN

ac代码

#include<cstdio>
#include<algorithm>
using namespace std;

int a[400005];
int d[400005];

int main()
{
    int n;
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    if (n==0)  //0个元素特判一下 
    {
        printf("0\n");
        return 0;
    }
    d[1]=a[1];  //初始化 
    int len=1;
    for (int i=2;i<=n;i++)
    {
        if (a[i]>=d[len]) d[++len]=a[i];  //如果可以接在len后面就接上,如果是最长上升子序列,这里变成> 
        else  //否则就找一个最该替换的替换掉 
        {
            int j=upper_bound(d+1,d+len+1,a[i])-d;  //找到第一个大于它的d的下标,如果是最长上升子序列,这里变成lower_bound 
            d[j]=a[i]; 
        }
    }
    printf("%d\n",len);    
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_42079027/article/details/81348524
今日推荐