POJ 2774 Long Long Message

来源:互联网 发布:win10点网络图标没反应 编辑:程序博客网 时间:2024/05/20 21:45

http://poj.org/problem?id=2774

题意:给定两个字符串 A 和 B ,求最长公共子串。


思路:后缀数组。(摘自罗穗骞的国家集训队论文)字符串的任何一个子串都是这个字符串的某个后缀的前缀。求 A 和 B 的最长公共子串等价于求 A 的后缀和 B 的后缀的最长公共前缀的最大值。如果枚举A和 B 的所有的后缀,那么这样做显然效率低下。由于要计算 A 的后缀和 B 的后缀的最长公共前缀,所以先将第二个字符串写在第一个字符串后面,中间用一个没有出现过的字符隔开,再求这个新的字符串的后缀数组。观察一下,看看能不能从这个新的字符串的后缀数组中找到一些规律。以 A=“ aaaba ”,B=“ abaa ”为例,如图 8 所示。

poj <wbr>2774 <wbr>: <wbr>Long <wbr>Long <wbr>Message <wbr>(后缀数组)
    
    那么是不是所有的 height 值中的最大值就是答案呢?不一定!有可能这两个后缀是在同一个字符串中的,所以实际上只有当suffix(sa[i-1])和suffix(sa[i]) 不是同一个字符串中的两个后缀时,height[i]才是满足条件的。而这其中的最大值就是答案。记字符串 A 和字符串 B 的长度分别为|A|和|B|。求新的字符串的后缀数组和 height 数组的时间是 O(|A|+|B|) ,然后求排名相邻 但原来不在同一个字符串中的两个后缀的height值的最大值,时间也是O(|A|+|B|),所以整个做法的时间复杂度为 O(|A|+|B|) 。时间复杂度已经取到下限,由此看出,这是一个非常优秀的算法。

代码:
#include<stdio.h>#include<string.h>#define MAXN 200010#define MAX(a,b) (a) > (b) ? (a): (b) int num[MAXN] ;char str[MAXN] ;int len1 , len2 ,N,M;int sa[MAXN] , rank[MAXN] ,height[MAXN] ;int wa[MAXN] , wb[MAXN] ,wv[MAXN],wd[MAXN] ; int cmp(int *r , int a , int b , int l){return r[a] == r[b] && r[a+l] == r[b+l] ;}void DA(int *r,int n,int m){//O(NlogN)int i, j , p , *x=wa, *y=wb,*t ;for( i = 0 ; i < m ; i++ ) wd[i] = 0 ;for( i = 0 ; i < n ; i++ ) wd[x[i]=r[i]] ++ ;for( i = 1 ; i < m ; i++ ) wd[i] += wd[i-1] ;for( i = n-1 ;i >= 0 ; i-- ) sa[--wd[x[i]]] = i ;for( j = 1 , p = 1 ; p < n; j *= 2 , m=p ){for( p = 0 , i = n-j ; i < n ; i++)y[p++] = i ;for( i = 0 ; i < n ; i++ )if(sa[i] >= j)y[p++] = sa[i] - j ;for( i = 0 ; i < n ; i++)wv[i] = x[y[i]] ;for( i = 0 ; i < m ; i++)wd[i] = 0 ;for( i = 0 ; i < n ; i++)wd[wv[i]] ++ ;for( i = 1 ; i < m ; i++)wd[i] += wd[i-1] ;for( i = n-1 ; i >= 0 ; i--)sa[ --wd[wv[i]]] = y[i] ;for( t = x , x = y , y = t , p = 1 , x[ sa[0] ] = 0,i = 1;i < n ; i++){x[sa[i]] = cmp( y ,sa[i-1] ,sa[i] , j ) ? p-1: p++;} }} void calHeight(int *r , int n){int i , j , k = 0 ;for( i = 1 ; i <= n ; i++)rank[sa[i]] = i ;for( i = 0 ; i < n ; height[ rank[i++]]=k){for( k ? k-- : 0 , j=sa[rank[i]-1]; r[i+k]==r[j+k] ; k++) ;}}int main(){int k ;while(scanf("%s",str) == 1){len1 = strlen(str);k = 0 ;for(int i=0;i<len1;i++){num[k++] = str[i] - 'a' + 2 ;}num[k++] = 0 ;//想到于'#' scanf("%s",str);len2 = strlen(str);for(int i=0;i<len2;i++)num[k++] = str[i] - 'a' + 2 ;N = len1 + len2  ;M = 30;DA(num,N+1,M);calHeight(num,N);int ans = 0 ;for(int i=2;i<=N;i++){if( (sa[i]<len1&&sa[i-1]>len1) || (sa[i]>len1&&sa[i-1]<len1) ){ans = MAX(ans , height[i]);}}printf("%d\n",ans);}return 0 ;}



原创粉丝点击