题目网址:https://www.luogu.com.cn/problem/B3637
题目描述
这是一个简单的动规板子题。
给出一个由 n(n≤5000)个不超过 10^6 的正整数组成的序列。请输出这个序列的最长上升子序列的长度。
最长上升子序列是指,从原序列中按顺序取出一些数字排在一起,这些数字是逐渐增大的。
输入格式
第一行,一个整数 n,表示序列长度。
第二行有 n 个整数,表示这个序列。
输出格式
一个整数表示答案。
输入输出样例
输入 #1
6
1 2 4 1 3 4
输出 #1
4
说明/提示
分别取出 1、2、3、4 即可。
看完题最先想到的是使用数组f[i]表示以第i个数字结尾的最长上升子序列,a[1]~a[i-1]中小于a[i]的f最大值,这里用max[f]表示,f[i]=max[f]+1.
实现也不难,使用双重循环枚举,时间复杂度为1+2+3+...+(n-1)=n^2/2,对于本题中n<=5000来说可以满分,但是如果数据范围再大一点,怎么做呢?
这个时候我们可以考虑使用f数组来存储上升子序列,再使用变量sum来存储子序列的长度,最后只需输出sum即可。
要想实现这一点也不难:
我们首先将f[1]初始化为a[1];
接着向后循环,对于a[i](1<i<=n)来说,有以下几种情况:
1、a[i]>f[sum](sum既为f的长度,也为f中最大数字的位置),此时需更新f的长度以及最高位;
2、a[i]<f[1](f[1]为f中最小数),此时需更新f最低位;
3、f[1]<=a[i]<=f[sum]且a[i]不等于f中任意一个数,此时由于f中所有数都位于a[i]前面,且对于a[i]之后的数,我们要使f中任意的上升子序列中最大数尽可能最小,所以我们需要执行的操作为找出一个数v,使f[v-1]<a[i]且f[v]>a[i],此时将f[v]赋值为a[i]。在执行完上述操作后,我们既保证不会混乱其他子序列的最高位,也使得此子序列中的最高位最小。
4、f[1]<=a[i]<=f[sum]且a[i]等于f中某一个数,此时更新与不更新没有差别,可以不做操作。
将上述操作转换为代码即为:
1、
if(a[i]<f[1]) f[1]=a[i];
2、
if(a[i]>f[sum])
{
sum++;
f[sum]=a[i];
}
3、对这操作,我们如果只是枚举f[1]~f[sum]我们同样会超时,这个时候,我们发现f数组中的数全是由小到大排列的,这个时候我们需要找到其中一个符合条件的数,可以使用二分法来实现:
定义l=1;r=sum;mid=l=(l+r)/2;
此时判断f[mid]与a[i]的关系:
如果f[mid]<a[i],可以确定mid不是要找的v,可以使l=mid+1;
如果f[mid]>a[i],无法确定mid是不是要找的v,但可以确定mid+1不是要找的v,可以使r=mid;
如此循环往复,即可使l=r,则此时l与r既为要找的v;
l=1;
r=sum;
while(l<r)
{
mid=(l+r)/2;
if(f[mid]<a[i]) l=mid+1;
else if(f[mid]>a[i]) r=mid;
}
if(l==r) f[r]=a[i];
4、在循环时如果f[mid]=a[i],此时直接break跳出即可,添加一行判断语句即可。
l=1;
r=sum;
while(l<r)
{
mid=(l+r)/2;
if(f[mid]<a[i]) l=mid+1;
else if(f[mid]>a[i]) r=mid;
else break;
}
if(l==r) f[r]=a[i];
代码:
#include<bits/stdc++.h>
using namespace std;
int a[5005];//存储数组
int f[5005];//存储上升子序列
int main()
{
int n,i,j,,sum=1,l,r,mid;//n:数列长度 i,j:循环变量 sum:最长的子序列长度以及f中最高位 l,r,mid:二分用变量
cin>>n;
for(i=1;i<=n;i++)
{
cin>>a[i];
if(i==1) f[1]=a[1];//更新初始值
else if(a[i]<f[1]) f[1]=a[i];//更新最小值
else if(a[i]>f[sum])//更新最大值
{
sum++;//更新后长度加1
f[sum]=a[i];
}
else
{
l=1;
r=sum;
while(l<r)
{
mid=(l+r)/2;
if(f[mid]<a[i]) l=mid+1;//确定不是v的范围f[1]~f[mid]
else if(f[mid]>a[i]) r=mid;//确定不是v的范围f[mid+1]~f[sum]
else break;//f[mid]等于a[i]时直接跳出
}
if(l==r)//判断是否直接跳出,跳出则l<r
{
f[r]=a[i];//赋值使子序列最高位最小
}
}
}
cout<<sum;//输出最长子序列长度(f更新到的最远位置)
return 0;
}