51nod1134-----最长上升子序列

朴素做法

之前写过一个最长上升子序列的朴素做法

也很好理解  就是 dp[ i ]代表以 i 为结尾的最长递增子序列的长度  

那么  dp[ i ]就等于 a[ i ] 前面比a[ i ]小的所有的数的dp值中最大的那个+1

代码如下

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
#define maxn 1010
int a[maxn],dp[maxn];
int n,ans;
int main(){
    while(~scanf("%d",&n)){
    	ans=0;
        for(int i=0;i<n;i++)cin>>a[i],dp[i]=1;
        for(int i=1;i<n;i++){
            for(int j=0;j<i;j++){
                if(a[j]<a[i])
                    dp[i]=max(dp[j]+1,dp[i]);
                    ans=max(ans,dp[i]);
            }
        }
        cout<<ans<<endl;
    }
 
}

优化 〇(nlogn)

上面这个做法的复杂度是O(n^2)   有些时候会超时

比如这道题 51nod1134

所以 我们用另一种求最长上升子序列的办法  复杂度是O(nlog(n))

参考了聚聚的博客 地址如下http://www.cnblogs.com/GodA/p/5180560.html

我们举一个例子:有以下序列A[]=3 1 2 6 4 5 10 7,求LIS长度。

  我们定义一个B[i]来储存可能的排序序列,len为LIS长度。我们依次把A[i]有序地放进B[i]里。(为了方便,i的范围就从1~n表示第i个数)

  A[1]=3,把3放进B[1],此时B[1]=3,此时len=1,最小末尾是3

  A[2]=1,因为1比3小,所以可以把B[1]中的3替换为1,此时B[1]=1,此时len=1,最小末尾是1

  A[3]=2,2大于1,就把2放进B[2]=2,此时B[]={1,2},len=2

  同理,A[4]=6,把6放进B[3]=6,B[]={1,2,6},len=3

  A[5]=4,4在2和6之间,比6小,可以把B[3]替换为4,B[]={1,2,4},len=3

  A[6]=5,B[4]=5,B[]={1,2,4,5},len=4 

  A[7]=10,B[5]=10,B[]={1,2,4,5,10},len=5

  A[8]=7,7在5和10之间,比10小,可以把B[5]替换为7,B[]={1,2,4,5,7},len=5

  最终我们得出LIS长度为5。但是,但是!!这里的1 2 4 5 7很明显并不是正确的最长上升子序列。是的,B序列并不表示最长上升子序列,它只表示相应最长子序列长度的排好序的最小序列。这有什么用呢?我们最后一步7替换10并没有增加最长子序列的长度,而这一步的意义,在于记录最小序列,代表了一种“最可能性”。假如后面还有两个数据8和9,那么B[6]将更新为8,B[7]将更新为9,len就变为7。读者可以自行体会它的作用。

  因为在B中插入的数据是有序的,不需要移动,只需要替换,所以可以用二分查找插入的位置,那么插入n个数的时间复杂度为〇(logn),这样我们会把这个求LIS长度的算法复杂度降为了〇(nlogn)。

注意  这个方法求出来的 B数组  并不是最长上升子序列  只是长度上得到了最长上升子序列的长度 

而数值上  算法中的置换  是为了 考虑 “最长”的情况  因此顺序并不是最长子序列顺序 

甚至有可能会 B中的序列在原序列里顺序都是乱的  (仔细思考下)

下面给出我的写法

题目链接传送门51nod1134

两个写法

一发STL     lower_bound()

注意 这个函数会在 [ l,r)中二分查找 返回刚好大于等于val的值的地址  若没有  则返回 r 的地址

upper_bound()同理  返回刚好小于val的地址   用法一致  区间都是左闭右开  找不到都返回最后 r

返回的都是地址  所以需要减去 首地址 才得到位置

代码

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=50000+7;
int b[maxn],a[maxn];
int LIS(int a[],int n){
	int len=1;b[0]=a[0];
	for(int i=1;i<n;i++){
		int flag=lower_bound(b,b+len,a[i])-b;//减去首地址
		if(flag==len){
			b[len]=a[i];len++;
		}
		else{
			if(b[flag]!=a[i]) b[flag]=a[i];
		}
	}
	return len;
}
int n;
int main(){
	scanf("%d",&n);
	for(int i=0;i<n;i++){
		scanf("%d",&a[i]);
	}
	printf("%d",LIS(a,n));
}

一发手打二分查找

代码

注意 这里和普通的二分有一点区别就是  普通二分是查找val  这里是查找大于等于val的值

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=50000+7;
int b[maxn],a[maxn];
int find(int a[],int l,int r,int val){//这里范围是左闭右闭  可以根据题目需要 随意变动  
	int mid;
	if(a[r]<val) return r+1;
	while(l<=r){
		mid=(l+r)/2;
		if(a[mid]==val) return mid;
		if(l==r) return mid;
		if(a[mid]<val) l=mid+1;//查找大于等于val的数 
		if(a[mid]>val) r=mid;
	}
}
int LIS(int a[],int n){
	int len=1;b[1]=a[0];
	for(int i=1;i<n;i++){
		int flag=find(b,1,len,a[i]);
		if(flag==len+1){
			b[len+1]=a[i];len++;
		}
		else{
			if(b[flag]!=a[i]) b[flag]=a[i];
		}
	}
	return len;
}
int n;
int main(){
	scanf("%d",&n);
	for(int i=0;i<n;i++){
		scanf("%d",&a[i]);
	}
	printf("%d",LIS(a,n));
}

猜你喜欢

转载自blog.csdn.net/holly_Z_P_F/article/details/81558454
今日推荐