题目描述:
这只一道简单的动规模板题。
给出一个由n(n≤5000)个不超过10⁶的正整数组成的序列。请输出这个序列的最长上升子序列的长度。
最长上升子序列是指,从原序列中按顺序取出的一些数字排在一起,这些数字是逐渐增大的。
输入格式:
第一行,一个整数n,表示序列长度。
第二行有n个整数,表示这个序列。
输出格式:
一个整数表示答案。
输入输出样例:
输入:
6
1 2 4 1 3 4
输出:
4
思路一:
由于数据范围较小,可考虑采用枚举法完成
int f[5005],a[5005]; //分别表示前i个数字中最长上升子序列的长度和输入的n个正整数
核心代码:
for(i=1;i<=n;i++)//遍历这n个数
{
for(j=1;j<=i-1;j++)//在前i-1个数中找到符合要求最长的上升子序列
{
if(a[i]>a[j]) f[i]=max(f[i],f[j]);
//要求找到的子序列最后的数小于a[i];
}
f[i]++;//加上a[i]形成一个新的子序列
}
注意最长上升子序列不一定是f[n];遍历寻找最长上升子序列。
示范代码如下:
#include<bits/stdc++.h>
using namespace std;
int f[5005],n,m,a[5005];
int main(){
int i,j,k;
cin>>n;
for(i=1;i<=n;i++)
{
cin>>a[i];
}
for(i=1;i<=n;i++)
{
for(j=1;j<=i-1;j++)
{
if(a[i]>a[j]) f[i]=max(f[i],f[j]);
}
f[i]++;
}
int ans=0;
for(i=1;i<=n;i++)
{
ans=max(ans,f[i]);
}
cout<<ans;
return 0;
}
思路二:
这是针对较大数据范围所使用的思路。
int a[5005],h[5005];分别表示这n个数和前面的数组成的长度为i最长上升子序列中末尾最小的数字
整体思路为贪心+二分:
首先查找到a[i]能够更新的值(若a[i]刚好小于某一上升子序列的末尾数字)
for(j=1;j<=len;j++){
if(a[i]<h[j]){
len=max(len,j+1);//是否改变序列长度
f[j+1]=a[i];//更改h[i]
break;
}
}
此段代码可用二分提高查找效率(因为h[i]数组中的值一定是递增的),具体改为:
l=0;r=len;
while(l<r){
int mid=(l+r+1)/2;
if(a[i]>f[mid])l=mid;//注意边界问题
else r=mid-1;
}
len=max(len,r+1);
f[r+1]=a[i];
图示过程如下:
至于贪心,只有保证每个上升子序列的末尾数字尽可能地小,才能寻找到最长的上升子序列。
最后输出len就能得到最长上升子序列长度。
示范代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=100005;
int a[N],f[N],n,m;
int main(){
int i,j,k,l,r,mid,len=0;
cin>>n;
memset(f,-1,sizeof(f));
for(i=1;i<=n;i++){
cin>>a[i];
l=0;r=len;
while(l<r){
int mid=(l+r+1)/2;
if(a[i]>f[mid])l=mid;
else r=mid-1;
}
len=max(len,r+1);
f[r+1]=a[i];
}
cout<<len;
return 0;
}