最长上升(不下降)序列(STL)

一.朴素算法

关于最长上升或不下降的子序列的朴素求法我就不用说了吧,直接一个$$O(n^2)$$的时间复杂度,附代码:

#include <cstdio> 
#define M 100005 
#define r register //加速器而已
#define max(a, b) a > b ? a : b 
int n, a[M], LIS[M], ans; //a是原数组
int main (){ 
    scanf("%d", &n); 
    for(r int i = 1; i <= n; i ++){ 
        scanf("%d", &a[i]); 
        LIS[i] = 1; 
    } 
    for(r int i = 1; i <= n; i ++){ 
        for(r int j = 1; j < i; j ++){ 
            if(a[j] < a[i]) 
                LIS[i] = max (LIS[i], LIS[j] + 1); 
        } 
    } 
    for(r int i = 1; i <= n; i ++) 
        ans = max (ans, LIS[i]); 
    printf("%d\n", ans); 
    return 0; 
} 

但这样的时间复杂度在处理大数据的时候太浪费时间了,于是我们引进STL。


二.STL

在这里,我们先引进两个函数:

1.lower_bound与upper_bound

我在这里简单释义一下:lower_bound(起始位置,结束位置,查找值):返回数组中第一个大于或等于查找值的元素的位置

                                       upper_bound(起始位置,结束位置,查找值):返回数组中第一个大于查找值的元素的位置

2.实践思路

思路很简单,就是:每个数组元素放进LIS数组时都用 lower_bound或upper_bound找到LIS数组中第一个大于或等于这个元素的数的位置,把那个数给替换掉。就比如:

放进前LIS:1 2 3 7 8

待放进元素: 5

放进后LIS:1 2 3 5 8

那么,为什么这样做呢?道理也很简单,我们把第一个大于或等于待放进元素的数替换掉,就给后面进入LIS数组的元素提供了更多的空间,能尽量保证到LIS数组中元素最多。就比如:

放进前LIS:1 2 3 7 8

待放进元素:5 6 8

放进后LIS:1 2 3 5 6 8

懂了吧?

3.代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define M 200005
int a[M], lis[M], n, len;
inline void Read (int &x){
    int f = 1; x = 0; char c = getchar();
    while (c > '9' || c < '0') {if (c == '-') f = -1; c = getchar();}
    while (c >= '0' && c <= '9') {x = x * 10 + c - 48; c = getchar();}
    x *= f;
}
int main (){
    Read (n);
    for (register int i = 1; i <= n; i ++)
        Read (a[i]);
    for (register int i = 1; i <= n; i ++){
        if (a[i] > lis[len])
            lis[++ len] = a[i];
        else{
            int idx = lower_bound (lis + 1, lis + len + 1, a[i]) - lis;
            lis[idx] = a[i];
        }
    }
    printf ("%d\n", len);
    return 0;
}

三.运用题:变异最长上升序列

1.题目

题目描述

给出一个长度为N的整数序列,求出包含它的第K个元素的最长上升子序列。

输入

第一行两个整数N, K
第二行N个整数

输出

如题目所说的序列长度。

样例输入

8 6

65 158 170 299 300 155 207 389

样例输出

4

 2.思路

怎么样,这道题目很简单吧,可以用两个最长上升子序列实践。

对于k以前的元素,去掉比k大或等于的元素;对于k后面的元素,去掉比k小或等于的元素。然后在k前面和后面分别找一个最长上升序列,把两个最长上升序列的元素加起来再加上k就行了。

3.代码

#include <cstdio> 
#include <cstring> 
#include <iostream> 
#include <algorithm> 
using namespace std; 
#define M 200005 
int a[M], lis[M], n, k, len, ans; 
bool flag[M]; 
inline void Read (int &x){ 
    int f = 1; x = 0; char c = getchar(); 
    while (c > '9' || c < '0') {if (c == '-') f = -1; c = getchar();} 
    while (c >= '0' && c <= '9') {x = x * 10 + c - 48; c = getchar();} 
    x *= f; 
} 
int main (){ 
    Read (n); 
    Read (k); 
    for (register int i = 1; i <= n; i ++) 
        Read (a[i]); 
    for (register int i = 1; i < k; i ++) 
        if (a[i] < a[k]) 
            flag[i] = 1; 
    for (register int i = k + 1; i <= n; i ++) 
        if (a[i] > a[k]) 
            flag[i] = 1; 
    for (register int i = k + 1; i <= n; i ++){ 
        if (!flag[i]) 
            continue; 
        if (a[i] > lis[len]) 
            lis[++ len] = a[i]; 
        else{ 
            int idx = lower_bound (lis + 1, lis + len + 1, a[i]) - lis; 
            lis[idx] = a[i]; 
        } 
    } 
    ans += len; 
    memset (lis, 0, sizeof(lis)); 
    len = 0; 
    for (register int i = 1; i <= k - 1; i ++){ 
        if (!flag[i]) 
            continue; 
        if (a[i] > lis[len]) 
            lis[++ len] = a[i]; 
        else{ 
            int idx = lower_bound (lis + 1, lis + len + 1, a[i]) - lis; 
            lis[idx] = a[i]; 
        } 
    } 
    ans += 1 + len; 
    printf ("%d\n", ans); 
    return 0; 
}

四.结语

以后的最长上升子序列问题就用STL吧!

猜你喜欢

转载自blog.csdn.net/weixin_43908980/article/details/86412721