关于LIS和LCS问题的o(nlogn)解法

Posted kyspring

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于LIS和LCS问题的o(nlogn)解法相关的知识,希望对你有一定的参考价值。

o(n^2)解法就不赘述了,直接解释o(nlogn)解法

LIS最长递增子序列;

先明确一个结论:在长度最大为len的递增序列里若末尾元素越小,该递增序列越容易和后面的子序列构造出一个更长的递增子序列。也即认为,长度为len的递增子序列中末尾元素最小的那种最需要保留。我们不妨称这个目前找到序列为到目前为止的 最优序列。

因此设置一个数组lis[i]其中 i 表示此时最大递增序列的长度,数组值表示此时达到 i 的最优序列(也即 长度为len的递增子序列中末尾元素最小的那种)的末尾元素。

那么此时只需遍历一遍输入数据,维护lis的上述特性,则最后所得的lis数组的长度就是要求的len。

不多言,结合代码理解:

#include<iostream>
#include<cstdio>
using namespace std;
const int maxn=1e5+5;
int a[maxn];
int n;
int lis[maxn];
int len=1;
int find(int x){
    int l=1,r=len,m;
    while(l<r){
        m=l+(r-l)/2;
        if(lis[m]>=a[x]){//这里若去掉等号即为 非严格递增序列 
            r=m;
        } 
        else{
            l=m+1;
        }
    }
    return l;
}
int main(void){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    lis[1]=a[1];
    for(int i=2;i<=n;i++){
        if(a[i]>lis[len]){
            lis[++len]=a[i];
        }
        else{
            int pos=find(i);
            lis[pos]=a[i];
        }
    }
    printf("%d",len);
    return 0;
}

LCS最长公共子序列:

最长公共子序列 的 nlogn 的算法本质是 将该问题转化成 最长增序列(LIS),因为 LIS 可以用nlogn实现,所以求LCS的时间复杂度降低为 nlogn。

               假设有两个序列 s1[ 1~6 ] = { a, b, c , a, d, c }, s2[ 1~7 ] = { c, a, b, e, d, a, b }。

               记录s1中每个元素在s2中出现的位置, 再将位置按降序排列, 则上面的例子可表示为:

               loc( a)= { 6, 2 }, loc( b ) = { 7, 3 }, loc( c ) = { 1 }, loc( d ) = { 5 }。

               将s1中每个元素的位置按s1中元素的顺序排列成一个序列s3 = { 6, 2, 7, 3, 1, 6, 2, 5, 1 }。

               在对s3求LIS得到的值即为求LCS的答案。(这点我也只是大致理解,读者可以自己理解甚至证明。)

这里给出全排列情况下的代码(即两个序列长度相同,数字组成相同,无重复元素)

#include<iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;
const int maxn=1e6+5;
int n,len=0;
int lis[maxn];
int a[maxn];
int b[maxn];
int loc[maxn];
int find(int x){
    int l=1,r=len,m;
    while(l<r){
        m=l+(r-l)/2;
        //if(lis[m]>=b[x]){//智障错误,找了那么久。。 
        if(lis[m]>=x){
            r=m;
        }
        else  l=m+1;
    }
    return l;
}
int main(void){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=n;i++){
        scanf("%d",&b[i]);
        loc[b[i]]=i;
    }
    for(int i=1;i<=n;i++){
        b[i]=loc[a[i]];
    }
//    for(int i=1;i<=n;i++)printf("%d",b[i]) ;// 
//    printf("\n");
    if(n!=0)lis[++len]=b[1];
    for(int i=2;i<=n;i++){
        if(b[i]>lis[len]){
            lis[++len]=b[i];
        }
        else{
            int pos=find(b[i]);
            lis[pos]=b[i];
        }
    }
    printf("%d",len);
    return 0;
}

 

以上是关于关于LIS和LCS问题的o(nlogn)解法的主要内容,如果未能解决你的问题,请参考以下文章

最长递增子序列(LIS) 贪心+二分详解O(nlogn)

LIS最长上升子序列O(nlogn)算法

bzoj1264 基因匹配Match (lcs转lis lcs(nlogn))

LIS LCS LCIS

动态规划 | 对输入进行hash处理的LIS 1045

最长递增子序列 LIS 时间复杂度O(nlogn)的Java实现