~~~~涨知识啦~~~~
概念
设有一个正整数序列a[n]: a1,a2,...,an ,对于下标i1<i2<...<ih,若有ai1<ai2<...<aih, 则称序列a[n]含有一个长度为h的不下降子序列。
那么最长不下降子序列就是求 h 的最大值咯,这很好理解
先来最最基础版本的
线性DP(n^2 算法)
这就是各大教材上讲的方法
记录 f [i] 表示以 i 结尾的最长不下降序列的长度
更新:对于当前的 j ,枚举 i (i < j ),如果 a[i] < a[j] 则 f[j] =max ( f[i] ) + 1
最后答案就是再O(n)扫一遍,取max
(n log n 算法)
这个叫什么呢?算了就name it 优秀算法吧
其实也没什么特别的,就是多了一个数组 d[k] 来存长度为k的最长不下降子序列的最后一位
如果当前的 a[j] 大于 d[k] 就将k++,并用 a[j] 更新此时的 d[k]
如果小于等于 d[k] 就二分 d 数组找到 a[j] 的前驱(小于a[j] 的最大值)并更新 d[k+1]
最后答案就是 k ,由于O(n)扫一遍,并在此基础上进行了二分,就是 nlogn了
为什么可以二分呢?
因为d数组是单调递增的,这很显然:长度更长了,d[k]的值是不会减小的
这个是用来求最长不下降子序列的,其实最长不上升子序列也可以这样做
思路都一样,就是初始值这种细节注意一下即可
下面有两道例题,分别是求最长不下降子序列和不上升的
例题一
最长不下降序列【加强版】
题目描述
设有由 n 个不相同的非负整数组成的数列,记为:b(1)、b(2)、……、b(n) 且 b(i)≠b(j)(i≠j),若存在 i1 < i2 < i3 < … < ie 且有 b(i1) < b(i2) < … < b(ie) ,则称为长度为 e 的不下降序列。程序要求,当原数列出之后,求出最长的不下降序列。
例如:13,7,9,16,38,24,37,18,44,19,21,22,63,15。例中 13,16,18,19,21,22,63 就是一个长度为 7 的不下降序列,同时也有 7 ,9,16,18,19,21,22,63 长度为 8 的不下降序列。
输入格式
第一行为一个正整数 n 。
第二行为 n 个非负整数序列,每个数不超过 10000 。
输出格式
输出一个正整数,为最长的不下降序列的长度。
样例数据 1
输入
14
13 7 9 16 38 24 37 18 4 19 21 22 63 15
输出
8
备注
【数据范围】
对于 30% 的数据,n≤1000;
对于 70% 的数据,n≤5000;
对于 100% 的数据,n≤100000;
分析
这数据范围一看就知道n^2 会炸
所以……上板子
代码
#include<bits/stdc++.h>
#define N 500000
#define in read()
using namespace std;
inline int read(){
char ch;int f=1,res=0;
while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
while(ch>='0'&&ch<='9'){
res=(res<<3)+(res<<1)+ch-'0';
ch=getchar();
}
return f==1?res:-res;
}
int n,a[N],d[N];
int main(){
n=in;
int k=1;
for(int i=1;i<=n;++i){ a[i]=in; }
d[0]=-1;//attention
d[1]=a[1];
for(int i=2;i<=n;++i){
if(d[k]<a[i]){ k++;d[k]=a[i];}
else{
int l=0,r=k,ans;
while(l<=r){
int mid=l+r>>1;
if(d[mid]<a[i]) ans=mid,l=mid+1;
else r=mid-1;
}
d[ans+1]=a[i];
}
}
printf("%d",k);
return 0;
}