LIS优化(队列优化+二分查找)
在介绍LIS之前先提供两个STL模板,lower_bound和upper_bound的模板,这两个分别是二分查找的上下界。
1 int lower(int x,int y) 2 { 3 int mid; 4 while(x<y){ 5 mid=x+(y-x)/2;//注意这里和(x+y)/2的区别!!! 6 if(a[mid]>=m) y=m; 7 else x=m+1; 8 } 9 return x; 10 }
1 int upper(int x,int y) 2 { 3 int mid; 4 while(x<y){ 5 mid=x+(y-x)/2; 6 if(a[mid]<=m) x=m+1; 7 else y=m; 8 } 9 return x; 10 }
接下来提供LIS的模板(求最大上升子序列O(n2)-> O(nlogn)),也可看洛谷的P1020的SPJ。
暴力枚举即每当插入一个新元素搜索前面的最大可能,dp方程为:
1 for(int j=1;j<i;j++) 2 if(a[i]>a[j]) 3 dp[i]=max(dp[i],dp[j]+1);
那么我们提供如下的一种思路,假设我们现在有一个到i-1状态的最大连续上升子序列(此队列由i-1个数已求出):
如果此时a[i]>a[i-1]那么直接插入就行,队列长度+1。
但是如果这个数小于或等于a[i-1]的话呢?我们才用强插的方式(doge)。
怎么插入呢?我们用logn的二分查找找出第一个大于等于a[i]的数,直接把他给replace掉,反正留着它也没什么用了,因为它没有后效性。
这样的插入是完美的,可用反证法证明它的正确性。
1 while(scanf("%d",&a[++n])!=EOF); n--; 2 s2[len2]=a[1]; 3 for(int i=2;i<=n;i++) 4 { 5 if(a[i]>s2[len2]) s2[++len2]=a[i]; 6 else{ 7 int p=lower_bound(s2+1,s2+len2,a[i])-s2; 8 s2[p]=a[i]; 9 } 10 }
那么下降呢,有的同学说这个lower_bound怎么找第一个小于它的数呢?
自己手写呗!(doge)|| 手写个排序 || 参考priority_queue的原型。(priority_queue<int,vector<int>,greater<int> > que)
最后介绍一个LIS优化的题目(uva1471)
这一题的求解方式我们用了两次优化。
第一次:我们先确定删除的子序列两端i和j,接下来往两边数. Obviously:O(n3)
第二次:我们先确定两个数组:g[i]表示以i结尾的上升子序列和f[i]表示的以i开始的上升子序列。初始化所用的时间O(n),总时间O(n2)
第三次:我们开LIS优化:用一个数组d[i]记录长度为 i 的连续递增序列的最后一个元素的最小值。用lower_bound够通过二分查找直接得到g[j]的值,进而得到一个可行的长度ans,然后更新数组d就可以。更新的方程如上面模板所求一致d[f[i]] = min(a[i], d[f[i]])。
注:本题还有set的解法,代码量有点大,有兴趣可找我要。
1 memset(dp,0x3f,sizeof(dp)); 2 scanf("%d",&n); 3 for(int i=1;i<=n;i++) scanf("%d",&a[i]); 4 g[1]=1; f[n]=1; 5 for(int i=2;i<=n;i++) 6 if(a[i]>a[i-1]) g[i]=g[i-1]+1; 7 else g[i]=1; 8 for(int i=n-1;i>=1;i--) 9 if(a[i]<a[i+1]) f[i]=f[i+1]+1; 10 else f[i]=1; 11 for(int i=1;i<=n;i++) 12 { 13 int len=lower_bound(dp+1,dp+1+n,a[i])-(dp+1)+f[i]; 14 ans=max(ans,len); 15 dp[g[i]]=min(dp[g[i]],a[i]); 16 }
以上为基本的LIS优化方法,主旨在排除没有用的点减少不必要的查找,一般用于做子序列问题的dp题目。