POJ 2774 Long Long Message

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 ;	
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值