求最长不下降/上升/下降/不上升子序列
说在前面的话
求最长不下降/上升/下降/不上升/子序列的原理都是一样的,只是有无等于号或者大于小于号的关系,懂了一个其余的就懂了,所以我这里只拿不下降子序列来举例子吧。
(n^2)的做法
原理
求最长不下降子序列的话,(i)位置应该和(i)位置之前的位置里面小于等于(i)位置上的值且最长不下降子序列最长的那个,所以可以枚举(i)位置之前的每一个点,然后按照上面说的选取就好了。
代码
#include<iostream>
#include<cstdio>
using namespace std;
const int Max = 201;
int n;
int a[Max],f[Max],c[Max];
int Ans[Max];
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;++ i)
scanf("%d",&a[i]),f[i] = 1;//输入数据将以每一个数结尾的最长不下降子序列标记为1
for(int i = 1;i <= n;++ i)
for(int j = 1;j < i;++ j)
if(a[i] >= a[j])//满足最长不下降 , 也就是大于等于
if(f[i] < f[j] + 1)
f[i] = f[j] + 1,c[i] = j;//记录
int ans = 0,qwq = 1;
for(int i = 1;i <= n;++ i)
if(f[i] > ans)//找最大的
ans = f[i],qwq = i;//记录最大的所在的下标
printf("max=%d
",ans);
int aaa = ans;
while(aaa --)//到这找回去
{
Ans[aaa + 1] = a[qwq];//用数组储存
qwq = c[qwq];
}
for(int i = 1;i <= ans;++ i)//正着输出
cout<<Ans[i]<<" ";
return 0;
}
nlogn的做法
这才是这篇文章的重点!!!
原理
开一个数组(注意是额外开一个数组,不是存储读入数字串的数组),第一个数理所当然的放在这个数组的第一个位置上面,如果进来的下一个数是符合不下降条件的,那么久放在他的后面,不然就在这个序列里面找到第一个比他大的数替换掉。
为什么要这样呢?
看完上面应该是有一种疑惑,这样替换的话会不会影响后面的呢?答案当然是否定的。
来说一下理由:
如果当前数组中放了1,3,5这三个数,下一个数是2,如果放在了第一个比他大的数的位置上,那就会放在3上面,这个时候数组中存储的数据就成了1,2,5,然后当下一个数进来的时候是和5比较是不是符合不下降的条件,所以把2替换在3的位置上面并没有影响。
具体说一下:就是如果这个数替换的那个数之后还有数,必然不会影响之后一个数拿过来之后的第一项操作,也就是先比较一下放在最后面是不是会符合不下降,那如果后面没有数了,他就是最后一个的话,用这个小的替换了前面的大的,也必然会使结果更优,所以没有问题。
代码
//最长不下降子序列nlogn Song
#include<cstdio>
#include<algorithm>
using namespace std;
int a[40005];
int d[40005];
int main()
{
int n;
scanf("%d",&n);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
if (n==0) //0个元素特判一下
{
printf("0
");
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
",len);
return 0;
}