一、算法分析
朴素的n^2算法已经不足以满足数据较大情况下的需求了,但是其还是充分体现了DP类问题中最简单的线性DP的思想,所以像紫书上讲线性结构DP的时候就提到n方算法的思想。对于n方的算法,在这里不进行描述,因为一本通上真的讲得很详细(有一个表一看就懂了)。这里我们将该问题优化到nlogn。
首先我们考虑一个dp一维数组,并假定这个数组里面已经存放了第i位之前的最优的子序列(这里的最优要求不但是最长的,而且是末尾元素值最小的,这样新添加的元素就更有可能加进去),然后我们只需要把这个序列的长度尽量往更长去推就行(进行状态转移)。推的方法就是我们每次往dp数组里放元素,这时有两种情况,一种是所添加的新元素大于dp末尾元素,那就直接添加,另一种是虽然小于末尾元素,但是必然在前面的最优段内能找到一个下界,举个例子
比如已有 1 3 5 9 的时候,如果新插入的值为10,那么当前的最优子序列就是 1 3 5 9 10,同时我们记录最优子序列长度加1,也就是相当于往更长的方向(也就是我们的目标)推了一位。如果新插入的值是4,那么虽然其小于末尾值9,但是还可以在前面找到下界,也就是原来最优序列中恰好大于插入值的那个位置,在这里也就是5的位置,将其替换,这样1 3 4 9就是一个更优解。但是我们在这里注意一点,此时dp数组的前三个元素组成了一个最优解(也就是插入位置之前形成最优解),但是1 3 4 9不是,因为我们发现在输入数据里面,4在9的后面,故而1 3 4 9是非法的。但是这在我们求最大长度的时候是不影响的。
下面是样例数据对应的dp数组
1 7
1 3
1 3 5
1 3 5 9
1 3 4 9
1 3 4 8
二、代码及注释
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
using namespace std;
const int maxn=1050;
int a[maxn];
vector<int> dp;
const int INF=0x7fffffff;
int erfen(int bg,int ed,int p){ //这里就是模板了,二分找序列中恰好大于p的值的下标
int m;
while(bg<ed){
m=bg+(ed-bg)/2;
if(dp[m]>p) ed=m;
else bg=m+1;
}
return bg;
}
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int n;
cin>>n;
dp.push_back(0); //我们不用第一个元素,下标从1开始
for(int i=1;i<=n;i++){
cin>>a[i];
dp.push_back(INF);
}
dp[1]=a[1]; //先把第一个放进去
int ans=1; //注意最后得到的dp数组不一定是我们的最长上升子序列,因为dp中有些位数是无效的,我们用ans记录有效位数
for(int i=2;i<=n;i++){
if(a[i]>dp[ans]) {ans++;dp[ans]=a[i];} //如果比末尾值要大的话,那么将这个值插入末尾是合法的,但不一定是最优的,我们暂且把它放在末尾
else{
int pos=erfen(1,ans,a[i]); //在前面找a[i]的可以插入的位置
dp[pos]=a[i];
}
}
cout<<ans<<endl;
//int len=dp.size();
//for(int i=1;i<len;i++) cout<<dp[i]<<' '; //在这里可以输出一下dp数组看看
return 0;
}
nlogn算法对应的时间是2ms,下面再附上朴素n方算法代码,时间是12ms
#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<queue>
#define ll long long
using namespace std;
const int maxn=1050;
const ll inf=0x7fffffff;
ll a[maxn],f[maxn];
ll n;
int main(){
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
f[i]=inf; //我们维护一个始终处于增序排列的数组f
}
f[1]=a[1];
int len=1;
for(int i=2;i<=n;i++){
int l=0,r=len,mid;
if(a[i]>f[len]) f[++len]=a[i];
else{
while(l<r){
mid=(l+r)/2;
if(f[mid]>a[i]) r=mid;
else l=mid+1;
}
f[l]=min(a[i],f[l]); //处理一下末尾,让它最小
}
}
cout<<len;
return 0;
}