蒟蒻的DP入门(02)百练2757 最长上升子序列(nlogn写法)

一、算法分析

朴素的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;
} 
发布了50 篇原创文章 · 获赞 7 · 访问量 1134

猜你喜欢

转载自blog.csdn.net/numb_ac/article/details/103560089