最长公共子序列nlogn算法

求两个序列的最大公共子序列(LCS),普遍的动态规划算法时间复杂度为O(n^2),在两个序列的长度很长的时候运行时间过长。

可以转化为最长上升子序列(LIS)来求,时间复杂度可以优化到nlogn。

1.首先说一下转化为LIS的方法:

  找到s1序列中每个元素在s2序列中的位置,并用这些在s2中的位置替换s1中对应的元素,注意s1中的元素在s2中当有多个位置(a1,a2,a3……)时,要降序排列(a1>a2>a3),这样求最长上升子序列的时候,可以保证从这些位置中只取出一个位置来构造上升子序列。如s1:a,b,c,a,d,c ;s2: c,a,b,c,d,a;s1中元素在s2中出现的位置依次是,a{2,6},b{3},c{1,4},d{5}所以构造的新的序列为2,6,3,4,1,2,6,3,5,4,1。

2.最长上升子序列的求法:

  首先看一般的做法,设f[i]为以第i个元素为结尾的最长子序列的长度,a[i]为第i个元素的值,状态转移方程f[i]=max(0,f[j])+1;(j<=i,a[j]<=a[i]),时间复杂度为O(n^2).
  
  现在,我们仔细考虑计算F[i]时的情况。假设有两个元素A[x]和A[y],满足(1)y < x < i (2)A[x] < A[y] < A[i] (3)F[x] = F[y]

  此时,选择F[x]和选择F[y]都可以得到同样的F[i]值,那么,在最长上升子序列的这个位置中,应该选择A[x]还是应该选择A[y]呢?

  很明显,选择A[x]比选择A[y]要好。因为由于条件(2),在A[x+1] … A[i-1]这一段中,如果存在A[z],A[x] < A[z] < A[y],则与选择A[y]相比,将会得到更长的上升子序列。

  再根据条件(3),我们会得到一个启示:根据F[]的值进行分类。对于F[]的每一个取值k,我们只需要保留满足F[i] = k的所有A[i]中的最小值。设D[k]记录这个值,即D[k] = min{ A[i] } ( F[i] = k )。

  注意到D[]的两个特点:

  (1) D[k]的值是在整个计算过程中是单调不上升的。//此处需要特别注意!!!关键之所在!

  (2) D[]的值是有序的,即D[1] < D[2] < D[3] < … < D[n]。

代码如下:

#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;

const int maxn = 100005;

/*a,b为两个序列,lis记录a中元素在b中的位置,d[i]为
长度i的上升子序列最后一个元素的值,pos[i][j]记录a中
值为i的元素在b中出现的第j个位置的序号*/

int a[maxn], b, lis[maxn*20],d[maxn*20];
vector<int> pos[maxn];
int main(){
    int len_a, len_b;
    scanf("%d%d", &len_a, &len_b);
    for (int i = 1; i <= len_a; i++)
        scanf("%d", a + i);
    for (int i = 1; i <= len_b; i++){
        scanf("%d", &b);
        pos[b+5000].push_back(i);
    }

    int len_lis = 1;
    for (int i = 0; i <= len_a; i++)
        for (int k = pos[a[i]+5000].size() - 1; k >= 0; k--)
            lis[len_lis++] = pos[a[i]+5000][k];

    d[1] = lis[1];
    int max_len_lcs = 1;
    for (int i = 2; i <= len_lis; i++){

/*如果lis[i]大于目前最长子序列的最后一个元素,
把lia[i]附在改序列后边,构成长度+1的最长序列,
同时更新d[]的值.
否则找到d[]中第一个大于lis[i]的位置k,将lis[i]
附在长度为k-1的序列后边,即变为长度为k的序列,因此
更新d[k]的值为lis[i]*/

        if (lis[i] > d[max_len_lcs])
            d[++max_len_lcs] = lis[i];
        else {
            int pos_greater_than_lis_i = lower_bound(d, d + max_len_lcs, lis[i]) - d;
            d[pos_greater_than_lis_i] = lis[i];
        }
    }

    printf("%d\n", max_len_lcs);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值