【mark】最长公共子序列(poj 1458+hdu 1159)

30 篇文章 0 订阅
22 篇文章 0 订阅

经典的问题,在各大博客上有数不清的好帖子

下面为最常见的n^2算法

recursive formula


#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
using namespace std;
int f[500][500];
char x[500],y[500];
int solve(int a,int b)
{
  int i,j;
  memset(f,0,sizeof(f));
  for(i=1;i<=a;i++)
  {
     for(j=1;j<=b;j++)
     {
        if(x[i]==y[j])
        {
            f[i][j]=f[i-1][j-1]+1;
        }
        else
        f[i][j]=max(f[i-1][j],f[i][j-1]);
     }
  }
  return f[a][b];
}
int main()
{
    int a,b;
    while(scanf("%s",&x[1])!=EOF)
    {
        scanf("%s",&y[1]);
        a=strlen(&x[1]);
        b=strlen(&y[1]);
        printf("%d\n",solve(a,b));

    }
    return 0;
}



第二种来自:点击打开链接

O(nlogn):


最长公共子序列(LCS)最常见的算法是时间复杂度为O(n^2)的动态规划(DP)算法,但在James W. Hunt和Thomas G. Szymansky 的论文"A Fast Algorithm for Computing Longest Common Subsequence"中,给出了O(nlogn)下限的一种算法。

 

定理:设序列A长度为n,{A(i)},序列B长度为m,{B(i)},考虑A中所有元素在B中的序号,即A某元素在B的序号为{Pk1,Pk2,..},将这些序号按照降序排列,然后按照A中的顺序得到一个新序列,此新序列的最长严格递增子序列即对应为A、B的最长公共子序列。

 

举例来说,A={a,b,c,d,b},B={b,c,a,b},则a对应在B的序号为2,b对应序号为{4,0},c对应序号为1,d对应为空集,生成的新序列为{2,4, 0, 1, 4, 0},其最长严格递增子序列为{0,1,4},对应的公共子序列为{b, c, b}

 

原论文的证明过程较复杂,其实可以简单的通过一一对应来证明。即证明A、B的一个公共子序列和新序列的一个严格递增子序列一一对应。

(1) A、B的一个公共子序列对应新序列的一个严格递增子序列

假设A、B的某一个公共子序列长度为k,则其公共子序列在A和B中可以写为

{Ai1,Ai2, ..., Aik}

{Bj1,Bj2, ..., Bjk}

 

如此有Ai1 = Aj1,Ai2 = Aj2, ...., Aik = Ajk, 考虑元素Bj1在B中的序号P(Bj1),则有

P(Bj1)< P(Bj2) < ... < P(Bjk)

注意此严格递增子序列属于新序列的一个子序列,因此得证

 

(2) 新序列的一个严格递增子序列对应A、B的一个公共子序列

设新序列的一个严格递增子序列{P1,P2, ..., Pk},任意两个相信的P不可能属于A中同一个元素,因为A中某元素在B中的序号按照降序排列,但此序列为严格递增序列,矛盾。所以每个P均对应于A中不同位置的元素,设为{Ai1, Ai2, ..., Aik}。

因为P是严格递增序列,则每个P也对应B中唯一的一个元素,假设为{Bj1,Bj2, ..., Bjk},由P的定义可知Ai1= Aj1, Ai2 = Aj2, ...., Aik = Ajk,因此得证。

 

实现上比较复杂,有以下几个步骤:

(1) 对序列B排序

(2) 计算A中每个元素在B中的序号,并构成新序列

(3) 使用LIS的方法计算最长严格递增子序列

(4) 获取最长公共子序列


性能分析:

(1) 排序复杂度为nlogn

(2) 获取一个元素在B中的序号的复杂度,最小为logn,最大为n,获取所有元素的复杂度为 nlogn === n*n

(3) LIS 复杂度为nlogn

因此总体复杂度在nlogn 到 n*n logn之间,但如果(2) 步骤中A中元素在B中的序号对数很少时,性能相当优越,在实际测试时,string 中均为小写字母,长度为10000的情况下,这种方法比普通的LCS快一倍以上;如果string 中的字符扩展成char,即0-255,则这种方法比普通的LCS快至少一个数量级。以下为代码实现,可以参考:

附个参考代码:

 
 
  1. #include <stdio.h>  
  2. #include <ctype.h>  
  3. #include <string.h>  
  4. #include <iostream>  
  5. #include <string>  
  6. #include <math.h>  
  7. #include <vector>  
  8. #include <queue>  
  9. #include <algorithm>  
  10.  
  11. using namespace std;  
  12.  
  13. const int maxn = 1501 ;  
  14. vector<int> location[26] ;  
  15. int c[maxn*maxn] , d[maxn*maxn] ;  
  16.  
  17. inline int get_max(int a,int b) {   return a > b ? a : b ;  }  
  18.  
  19. //nlogn 求lcs  
  20. int lcs(char a[],char b[])  
  21. {  
  22.     int i , j , k , w , ans , l , r , mid ;  
  23.     for( i = 0 ; i < 26 ; i++) location[i].clear() ;  
  24.     for( i = strlen(b)-1 ; i >= 0 ; i--) location[b[i]-'a'].push_back(i) ;  
  25.     for( i = k = 0 ; a[i] ; i++)  
  26.     {  
  27.         for( j = 0 ; j < location[w=a[i]-'a'].size() ; j++,k++) c[k] = location[w][j] ;  
  28.     }  
  29.     d[1] = c[0] ;   d[0] = -1 ;  
  30.     for( i = ans = 1 ; i < k ; i++)  
  31.     {  
  32.         l = 0 ; r = ans ;  
  33.         while( l <= r )  
  34.         {  
  35.             mid = ( l + r ) >> 1 ;  
  36.             if( d[mid] >= c[i] ) r = mid - 1 ;  
  37.             else l = mid + 1 ;  
  38.         }  
  39.         if( r == ans ) ans++,d[r+1] = c[i] ;  
  40.         else if( d[r+1] > c[i] ) d[r+1] = c[i] ;  
  41.     }  
  42.     return ans ;  
  43. }  
  44.  
  45. int main()  
  46. {  
  47.     char a[maxn] , b[maxn] ;  
  48.     while (~scanf("%s%s",a,b))  
  49.     {  
  50.         printf("%d/n",lcs(a,b));  
  51.     }  

 

本文出自 “karsbin@stephy” 博客,请务必保留此出处http://karsbin.blog.51cto.com/1156716/966387



相关题目:

POJ1458
POJ2250
POJ1159
基本的最长公共子序列

POJ2533
最长不减(不增)子序列,用方法1、2即可

WOJ1398
最长不减(不增)子序列,方法1、2超时


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值