再探LIS

昨天讲课的时候突然想起来LIS还有一个东西没搞懂。

又去研究了下。

LIS问题就是要求一个序列中最长不下降或上升子序列,而此问题应用较广,例如很多题会有这样的条件

对于i,j如果他们可以同时选取,则必有|a[i]-a[j]|  <=  |b[i]-b[j]|  (b[i]<b[j])

数学化归一下下得到 b[i]-b[j] <= a[i]-a[j] <= b[j]-b[i]

将i,j各放在一边 有

a[i]-b[i]  >= a[j]-b[j]…………①,

a[i]+b[i] <= a[j]+b[j]…………②.

那么我们按其中一个条件排序,即保证排序后的序列对于任意的i,j(i<j)都有①式或②式成立,然后再按另一个条件求LIS即可求出最多可以取多少个。

若要知道取几次可以取完,那么 根据 dilworth 定理, 等同于求其反条件(即原来非降现在就是下降)子序列长度。

那么下面介绍求LIS的几种方法

法一 O(n^2)暴力dp转移

令dp[i]表示以ai结尾的最长不下降子序列长度

则转移方程为dp[i]=max{1,dp[j]+1} a[j]<=a[i]

则max{dp[i]}就是答案

法二 对于上面的dp转移,显然可以用树状数组优化

可以做到(nlogn)

设有 f[i] 表示以大小为i结尾的不下降子序列的最长长度

则dp[i]=max{f[j]}+1,j<=h[i];

max{f[j]}用树状数组维护

具体如下

 1 int g() {
 2     memset(C,0,sizeof C);
 3     int ans=0;
 4     for(int i=1;i<=n;i++) {
 5         dp[i]=1;
 6         int t=query(h[i])+1;
 7         ans=max(ans,t);
 8         add(h[i],t);
 9     }
10     return ans;
11 }

注意:1.可以不保留dp数组,直接更新ans即可。

2.如果max{h[i]}过大,则需要离散化

法三 二分法求LIS O(nlogn)

设s[i]表示以长度为i的上升子序列的最后一位的最小值。

则每次可以用a[i]去更新s[i]数组

显然s数组是不下降的

1 int h[Maxn],s[Maxn],ans,n;
2 int f(){
3     s[ans=0]=-INF;
4     for(int i=1;i<=n;i++){
5         int v=h[i];
6         if(v>s[ans])s[++ans]=v;
7         else*lower_bound(s+1,s+ans+1,v)=v;
8     }return ans;
9 }

对于第6行得到新的答案,根据s数组的定义,显然正确。

然后如果v<=s[ans],则此时不能更新ans,但是可以更新s数组,如何更新呢?跟上面一样,我们要找到一个s[i] (< v) 则此时可以用这个s[i]来更新s[i+1],而实际上只有这一个更新是有意义的,因为对于<=i的更新只会使答案更劣,对于i+2及以上的不能保证序列合法。

更新之后就会更新s[i+1]的值,又s[i]是最大的满足s[i]<v的i,所以s[i+1]必是最小的>=v的值,用lower_bound就好了。

同理如果要求非降子序列 改成下面这样即可

1 int h[Maxn],s[Maxn],ans,n=1;
2 int f(){
3     s[ans=0]=-INF;
4     for(int i=1;i<=n;i++){
5         int v=h[i];
6         if(v>=s[ans])s[++ans]=v;
7         else*upper_bound(s+1,s+ans+1,v)=v;
8     }return ans;
9 }

可以发现改了两处,一是第6行的>= 二是lower_bound变成了upper_bound

请自行思考为什么这样可行

原文地址:https://www.cnblogs.com/showson/p/4666347.html