今天花了很长时间终于弄懂了这个算法……毕竟找一个好的讲解真的太难了,所以励志我要自己写一个好的讲解QAQ

这篇文章是在懂了这个问题n^2解决方案的基础上学习。

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

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

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

现在我们已知最长的不下降子序列长度为1,末尾元素的最小值为a[1],那么我们让i从2到n循环,依次求出前i个元素的最长不下降子序列的长度,循环的时候我们只需要维护好d这个数组还有len就可以了。

关键问题就是怎么维护?

可以看出我们是要用logn的复杂度维护的。实际上利用了d数组的一个性质:单调性。(长度更长了,d[k]的值是不会减小的)

考虑新进来一个元素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。

至此,我们就神奇的解决了这个问题。按照这个思路,如果需要求严格递增的子序列怎么办?

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

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

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

    同样的道理,只是换成lower_bound即可。每次复杂度logn。

--------2018.4.14更新--------

很久没看,没想到这篇文章有这么多人阅读了,感觉最长递增子序列这里讲的不是太清楚,因此补充一下。

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

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

那这个性质有什么意义呢?

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

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

  如果这个元素等于d[len],那么可以保证d[1..len-1]都是小于a[i]的(根据上面的证明),因此这个元素就没有什么意义了,直接忽略就好,因为它无法接在任何一个元素d后面产生一个更有优势的子序列。

  如果这个元素小于d[len],那么就在d数组中找到第一个大于等于它的元素(这个元素必然存在,至少d[len]就是),把这个元素替换成a[i]即可。

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

这样做肯定是对的,而邝斌的模板上实际上是求的最长不下降子序列,而不是最长上升子序列。不信可以测试一下"1 2 3 2 3 2"这个样例。切记,不要迷信权威,学会自己思考。

------------------------------------

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

//最长不下降子序列nlogn  Song 

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

想了一晚上这个问题终于想通了。前面说的“最该替换的位置”实际上不是很精确,那个位置替换掉是它唯一能够替换的位置,之所以要替换,就是为了维护d这个数组,让它始终满足最初的定义。

nlogn复杂度的最长上升子序列还有树状数组的写法,可以参考我的另一篇文章:https://www.cnblogs.com/acmsong/p/7231069.html

最长不下降子序列nlogn算法详解的更多相关文章

  1. hdu1025 最长不下降子序列nlogn算法

    C - DP Crawling in process... Crawling failed Time Limit:1000MS     Memory Limit:32768KB     64bit I ...

  2. 最长不下降子序列 nlogn && 输出序列

    最长不下降子序列实现: 利用序列的单调性. 对于任意一个单调序列,如 1 2 3 4 5(是单增的),若这时向序列尾部增添一个数 x,我们只会在意 x 和 5 的大小,若 x>5,增添成功,反之 ...

  3. 最长不下降子序列nlogn

    b[i]表示长度为i的最长不下降子序列的最小末尾元素的值显然它是单调递增的,满足二分性质,然后就可以愉快地二分啦. #include<iostream> #include<cstdi ...

  4. 最长上升子序列O(nlogn)算法详解

    最长上升子序列 时间限制: 10 Sec   内存限制:128 MB 题目描述 给定一个序列,初始为空.现在我们将1到N的数字插入到序列中,每次将一个数字插入到一个特定的位置.我们想知道此时最长上升子 ...

  5. 最长不下降序列nlogn算法

    显然n方算法在比赛中是没有什么用的(不会这么容易就过的),所以nlogn的算法尤为重要. 分析: 开2个数组,一个a记原数,f[k]表示长度为f的不下降子序列末尾元素的最小值,tot表示当前已知的最长 ...

  6. 「10.19」最长不下降子序列(DP)·完全背包问题(spfa优化DP)·最近公共祖先(线段树+DFS序)

    我又被虐了... A. 最长不下降子序列 考场打的错解,成功调了两个半小时还是没A, 事实上和正解的思路很近了,只是没有想到直接将前$D$个及后$D$个直接提出来 确实当时思路有些紊乱,打的时候只是将 ...

  7. 最长不下降子序列的O(n^2)算法和O(nlogn)算法

    一.简单的O(n^2)的算法 很容易想到用动态规划做.设lis[]用于保存第1~i元素元素中最长不下降序列的长度,则lis[i]=max(lis[j])+1,且num[i]>num[j],i&g ...

  8. 最长不下降子序列 (O(nlogn)算法)

    分析: 定义状态dp[i]表示长度为i的最长不下降子序列最大的那个数. 每次进来一个数直接找到dp数组第一个大于于它的数dp[x],并把dp[x - 1]修改成 那个数.就可以了 AC代码: # in ...

  9. 算法进阶 (LIS变形) 固定长度截取求最长不下降子序列【动态规划】【树状数组】

    先学习下LIS最长上升子序列 ​ 看了大佬的文章OTZ:最长上升子序列 (LIS) 详解+例题模板 (全),其中包含普通O(n)算法*和以LIS长度及末尾元素成立数组的普通O(nlogn)算法,当然还 ...

随机推荐

  1. 【多端应用开发系列1.1.1 —— Android:使用新浪API V2】服务器Json数据处理——Json数据概述

    [前白] 一些基础的东西本系列中就不再详述了,争取尽量写些必不可少的技术要点. 由于本系列把Web Service 构建放到了第二部分,Android项目就采用新浪微博API v2作为服务器端. [原 ...

  2. 完整cocos2d-x编译Andriod应用过程

    作者:何卫 转载请注明,原文链接:http://www.cnblogs.com/hewei2012/p/3366969.html 其他平台移植:http://cocos2d.cocoachina.co ...

  3. 06day1

    Rabbit Number 枚举 [问题描述] 设 S(N)表示 N 的各位数字之和,如 S(484)=4+8+4=16,S(22)=2+2=4.如果一个正整数 x满足 S(x*x)=S(x)*S(x ...

  4. Android提升进入界面的速度

    应用除了有内存占用.内存泄露.内存抖动等看不见的性能问题外,还有很多看得见的性能问题,比如进入界面慢.点击反应慢.页面卡顿等等,这些看得见的体验问题会严重影响用户使用APP心情,但用户的情绪又无法通过 ...

  5. Mysql线程池优化笔记

    Mysql线程池优化我是总结了一个站长的3篇文章了,这里我整理到一起来本文章就分为三个优化段了,下面一起来看看.     Mysql线程池系列一(Thread pool FAQ) 首先介绍什么是mys ...

  6. 【转】让apache支持中文路径或者中文文件

    本帖最后由 狂人阿川 于 2013-4-12 19:13 编辑 今天在给一美国VPS客户调试他的程序的时候.发现他的网站有中文名称.貌似apache无法认识中文路径,火狐下面能下载他的文件,IE下面不 ...

  7. information_schema中的三个关于锁的表

    在5.5中,information_schema 库中增加了三个关于锁的表(MEMORY引擎):innodb_trx         ## 当前运行的所有事务innodb_locks       ## ...

  8. 浅析基层检察院派驻乡镇检察室的健康发展 z

    时间:2011-03-22 10:08 作者:祝志方 新闻来源:正义网 一.前言 在我国,基层检察院派驻乡镇检察室的发展经过了一个曲折发展的历程,上世纪80年代,随着经济社会的发展,一批乡镇检察室应运 ...

  9. Write a program to convert decimal to 32-bit unsigned binary.

    Write a program to convert decimal to 32-bit unsigned binary. Write a program to convert a 32-bit un ...

  10. Bias/variance tradeoff

    线性回归中有欠拟合与过拟合,例如下图: 则会形成欠拟合, 则会形成过拟合. 尽管五次多项式会精确的预测训练集中的样本点,但在预测训练集中没有的数据,则不能很好的预测,也就是说有较大的泛化误差,上面的右 ...