LIS 最长不降子序列(二分篇)
就不说基本的N^2做法了
来说说二分做法,这个做法的复杂度(nlogn)
具体的看代码注释
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int main(){
int n,a[100005],f[100005],ans=0;//这里的a记录读入的数组,f这个数组来记录当前这个
//最长上升子序列最后一个数
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i]; //常规读入没啥好说的
}
int l,r,mid;//这里用到二分的思想
memset(f,-1,sizeof(f));//首先初始化数组为-1,为什么是-1呢?(往下看)
for(int i=1;i<=n;i++){
if(f[ans]<a[i]){//我们这里对于每一个ai,都要找到一个序列,使这个ai大于等于
//这个f[ans](这个子序列的末尾,就是这个序列中最大的一个)
f[ans+1]=a[i];//如果找到了,更新,这个子序列的长度++
ans++;
}
else{//如果这个数不符合,那么你就要重新找,此时我们以ai为立足点,我们要找离
//ai最近的子序列,为什么呢?
//这是因为我们运用了贪心的思想后能保证长度越大的
//不上升序列结束点越小,试着用当前数去更新长度为
//x的不上升序列的结束点(又是贪心的思想,只更新长度最长且结束点小于自己的
l=0,r=ans;//二分查找
while(l<r){
mid=(l+r)/2;
if(f[mid]>=a[i]) r=mid;//如果这个满足,那么我们既然要找最右边的那个
//,所以就把查找区间变成右半边
else{
l=mid+1;//如果没找到,就变成左半边
}
}
f[l]=a[i];
}
}
cout<<ans<<endl;
}
我们来优化一下,这里优化仅仅指代码量,这里介绍两个STL函数。
upper_bound 和 lower_bound
upper_bound(first,last,val)
这个函数,查找在first-last这个区间内,第一个大于val的值(以指针的形式返回)
lower_bound(first,last,val)
这个函数,查找在first-last这个区间内,第一个大于等于val的值(以指针的形式返回)
准确的说
lower_bound:返回指向范围 [first, last) 中首个不小于(即大于或等于) value 的元素的迭代器,或若找不到这种元素则返回 last 。
upper_bound:返回指向范围 [first, last) 中首个大于 value 的元素的迭代器,或若找不到这种元素则返回 last
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int main(){
int n,a[100005],f[100005],x=0,ans=0;
cin>>n;
for(int i=0;i<n;i++){
cin>>a[i];
}
memset(f,0x7f,sizeof(f));
f[0]=-1;
for(int i=0;i<n;i++){
int v=lower_bound(f,f+ans+1,a[i])-f;
ans=max(ans,v);
f[v]=min(f[v],a[i]);//这个f[v],存长度为v的序列的最后一个数
}
cout<<ans<<endl;
}
这个代码的逻辑和上一个代码的逻辑类似
对了!有个小tips,在使用upper_bound 和 lower_bound的时候
最后在括号外面一定要减一个f
因为是迭代器
还有就是,这个读入和循环的时候,应该是从0开始,这个指针是默认指队头0的
然后呢
给大家推荐一下练习题
[(https://www.luogu.org/problemnew/show/P1020)]
最后给大家安利一下大佬的博客
[https://me.csdn.net/Ted_Tong]
这个人今天也写了一篇关于LIS的博客,可以对比看看