lower_bound和upper_bound求最长上升(不下降)子序列O(nlogn)做法。

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

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

O(logn)的复杂度维护,实际上利用了dp数组的一个性质:单调性(因为我们本来就要求的一个不下降或上升子序列,虽然dp数组长度变,但是dp数组还是一个递增数组)。

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

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

  如果这个元素小于dp[len],说明它不能接在数组最后一个后面,这时候就要看它该接在谁后面。

       准确的说,并不是接在谁后面。而是替换掉谁。因为它接在前面的谁后面都是没有意义的,再接也超不过最长的len,所以是替换掉别人。这时候应该用它替换在dp数组中第一个大于它的。第一个意味着前面的都小于等于它(这点很好理解,本来dp数组就是单调的,我们找的是第一个大于它的元素的位置,这个位置之前的元素肯定都小于等于它),替换之后这个子序列比当前的dp[j]这个子序列更有潜力,因为我们要找最长的,所以我们要使当前位的数字尽可能小,让后面的数尽可能可以接在dp数组后面。

      按照这个思路,如果需要求严格递增的子序列怎么办?同样的道理,只是不下降序列可以存在相等的数,严格上升序列,说明不能存在相同的数,只是将upper_bound换成lower_bound即可。原因:因为upper_bound,是找第一个大于本身的数,要是dp数组里面有与该数一样的,会直接返回该数后面的位置,这个数则直接接在与它相等的数的后面,lower_bound是找第一个大于等于本身的数,要是有相等的数,直接返回跟它相等的数的位置,直接替换掉跟它相等数,这时候序列没有变的。

最长递增子序列和最长不下降子序列的不同之处在于,dp数组的单调性更严格了:一定是单调递增的。

可以用反证法来证明这一点:假设有dp[i]=dp[i+1],也就是长度为i+1的子序列最后一位最小是d[i+1],那假设这个子序列是a[1], a[2], ..., a[i+1],在这个子序列里面,a[i]<dp[i+1]肯定成立,不然就不是最长递增子序列了。那也就是说a[i]<dp[i]了,那a[1], a[2], ..., a[i]就构成了一个长度为i的子序列,而且最后一个数比dp[i]小。那dp[i]肯定就不符合定义了。

实际上可以发现,小于等于的时候可以统一按照lower_bound替换的方式来处理。

下面是最长不下降子序列的代码,注释里面说明了如何改成最长上升子序列。

#pragma comment(linker, "/STACK:102400000,102400000")

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>

const int mod = 998244353;
const int maxn = 1e3 + 5;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int a[maxn], dp[maxn];

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

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值