最长公共字序列(LCS)(转载)(个人感觉外国的算法翻译到国内后,有误!求指点!!!)

 看了翻译版的求最长单调最长子序列的O(nlog n)的算法,感觉有误。找到国外的该算法原始出处,发现看不懂。快哭了   

 我运用转换成最长单调子序列后,感觉算法存在着错误。

    比如:

        A:1 3 1 2

        B:1 3

        得到的结果是1,但是很明显正确结果应该是2。

求解释!!!!!!!这是为什么?????????????

 

最长公共子序列问题:

给定2个字符串,求其最长公共子串。如abcde和dbada的最长公共字串为bd。

动态规划:dp[i][j]表示A串前i个和B串前j个的最长公共子串的长度。

若A[i] == B[j] , dp[i][j] = dp[i-1][j-1] + 1;

否则 dp[i][j] = max(dp[i-1][j],dp[i][j-1]);

时间复杂度O(N*M)。

dp[i][j]仅在A[i]==B[j]处才增加,对于不相等的地方对最终值是没有影响的。

故枚举相等点处可以对其进行优化。

则对于dp[i][j](这里只计算A[i]==B[j]的i和j),取最大的dp[p][q],满足(p<i,q<j),通过二叉搜索树可以再logn的时间里获取到最大的dp[p][q],区间在[0,j)。

这里也可将其转化为最长递增子序列问题。

举例说明:

A:abdba

B:dbaaba

则1:先顺序扫描A串,取其在B串的所有位置:

    2:a(2,3,5) b(1,4) d(0)。

    3:用每个字母的反序列替换,则最终的最长严格递增子序列的长度即为解。

替换结果:532 41 0 41 532

最大长度为3.

简单说明:上面的序列和最长公共子串是等价的。

对于一个满足最长严格递增子序列的序列,该序列必对应一个匹配的子串。

反序是为了在递增子串中,每个字母对应的序列最多只有一个被选出。

反证法可知不存在更大的公共子串,因为如果存在,则求得的最长递增子序列不是最长的,矛盾。

最长递增子序列可在O(NLogN)的时间内算出。

dp[i] = max(dp[j]+1) ( 满足 a[i] > a[j] && i > j )

显然对于同样的如dp[k] = 3,假定k有多个,记为看k1,k2,.....,km 设k1 < k2 < .... < km

在计算dp[i]的时候,k2,k3,....,km显然对结果没有帮助,取当前最小的k,

满足ans[k] = p (最小的p使得dp[p]=k) ,每次二分,更新ans[dp[i]] = min(ans[dp[i]],i).

 

ps:LCS在最终的时间复杂度上不是严格的O(nlogn),不知均摊上是不是。

举个退化的例子:

如A:aaa

    B:aaaa

则序列321032103210

长度变成了n*m ,最终时间复杂度O(n*m*(lognm)) > O(n*m)。

 我自己写的代码:

#include <iostream>
#include <vector>
#include <cstdio>
#include <cstring>
using namespace std;

const int N = 1e5 + 10;
vector<int> iv[N];
int n,m,c[N],d[N],a[N],b[N];
void LCS()
{
    int i,j,k,w,ans,l,r,mid;
    for(int i = 0;i < N;++i) iv[i].clear();
    for(int i = n-1;i >= 0;--i)
       iv[a[i]].push_back(i);
    for(i = k = 0;i < m;++i)
      for(j = 0;j < iv[w=b[i]].size();++j,++k)
        c[k] = iv[w][j];
    d[1] = c[0];
    d[0] = -1;
    for(i = ans = 1;i < k;++i)
    {
        l = 0; r = ans;
        while(l<=r)
        {
            mid = (l+r)>>1;
            if(d[mid] >= c[i]) r = mid - 1;
            else l = mid + 1;
        }
        if(r == ans)
          ans++,d[r+1] = c[i];
        else if(d[r+1] > c[i])
          d[r+1] = c[i];
    }
    printf("%d\n",ans);
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        for(int i = 0;i < n;++i)
          scanf("%d",&a[i]);
        for(int i = 0;i < m;++i)
          scanf("%d",&b[i]);
        LCS();
    }
    return 0;
}


 

没事的时候优化了一哈代码:

#include <stdio.h>
#include <string.h>
#define CLR(a,v)memset(a,v,sizeof(a));
const int N = 1e5 + 10;

void LIC(int *A,int cnt)
{
    int b[N],k = 0;
    b[0] = -1;
    for(int i = 0;i < cnt;++i)
    {
        if(A[i] > b[k])
          b[++k] = A[i];
        else{
          int low = 1,high = k;
          while(low <= high)
          {
              int mid = (low+high)>>1;
              if(A[i] > b[mid])
                low = mid + 1;
              else
                high = mid - 1;
          }
          b[low] = A[i];
        }
    }
    printf("%d\n",k);
}
int main()
{
    int c[N],dp[N],n,m,val;
    while(~scanf("%d%d",&n,&m))
    {
        CLR(c,-1);
        int cnt = 0;
        for(int i = 1;i <= n;++i){
           scanf("%d",&val);
           c[val] = i;
        }
        for(int i = 1;i <= m;++i){
           scanf("%d",&val);
           if(c[val] != -1)
             dp[cnt++] = c[val];
        }
        LIC(dp,cnt);
    }
    return 0;
}


 

下面的地址是该算法的原始出处,表示英语太烂,看不懂。所以看了一篇翻译版。Click Here

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值