朴素做法
之前写过一个最长上升子序列的朴素做法
也很好理解 就是 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));
}