【九度0J】 题目1535:重叠的最长子串 (扩展KMP算法)(滚动哈希算法--Rabin-Karp算法)

题目描述:

给定两个字符串,求它们前后重叠的最长子串的长度,比如"abcde"和“cdefg”是"cde",长度为3。

输入:

输入可能包含多个测试案例。
对于每个测试案例只有一行, 包含两个字符串。字符串长度不超过1000000,仅包含字符'a'-'z'。

输出:

对应每个测试案例,输出它们前后重叠的最长子串的长度。

样例输入:
abcde cdefg

样例输出:

3

思路:扩展KMP,用extend[i]保存 主串 S[i.....n-1]与 模式串 T的最长公共前缀的长度,其中n是S的长度。

然后扫描一遍 extend[] 如 extend[i] == n-i 那么这个后缀的长度就是我们要求的值。

关于扩展KMP,可以去看论文:《求最长回文子串与最长重复子串》何林 的集训队论文

代码:

#include <iostream>
#include <cstring>
#include <cstddef>
#include <cstdio>

using namespace std;
const int MAXN = 1000001;
int next[MAXN];
int extend[MAXN];
char S[MAXN],T[MAXN];

void getnext(char *T)  
{  
    int k = 0;  
    int Tlen = strlen(T); 
    next[0] = Tlen;  
    while(k < Tlen-1 && T[k] == T[k+1]) 
		k++; 
    next[1] = k;  
    
    k = 1;
    for(int i = 2; i < Tlen; i++)  
    {  
        int p = k+next[k]-1, L = next[i-k];   // p :=已经匹配到的最远的位置
											// L :=   
        if((i-1)+L >= p)  
        {  
            int j=(p-i+1)>0? p-i+1:0;  
            while(i+j < Tlen && T[k+j]==T[j]) 
				j++;  
            next[i] = j;  
            k = i;  
        }  
        else next[k]=L;  
    }  
}  
  
void getextend(char *S,char *T)  
{  
    int a = 0;  
    getnext(T);  
    int Slen = strlen(S);  
    int Tlen = strlen(T);  
    int len = Slen<Tlen ? Slen:Tlen;  
    while(a < len && S[a] == T[a]) a++;  
    extend[0] = a;  
    a = 0;  
    for(int i = 1; i < Slen; i++)  
    {  
        int p = a + extend[a]-1, L = next[i-a];  
        
        if(i+L-1 >= p)
        {  
            int j = (p-i+1)>0? p-i+1:0;  
            
            while(i+j < Slen && j<Tlen && S[i+j] == T[j]) 
				j++;  
            extend[i] = j;  
            a = i;  
        }  
        else extend[i] = L;  
    }  
}

int main()
{
	while(scanf("%s%s",&S,&T) != EOF)
	{
		getextend(S,T);
		int n = strlen(S);
		int res = 0;
		for(int i = 0; i < n; i++)
		{
			if(extend[i] == n-i)
			{
				res = extend[i];
				break;
			}
		}
		cout<<res<<endl;
	}
	return 0;
}
 下面介绍一种滚动哈希算法:假设要求S的后缀和T的前缀相等的最大长度,也可以利用滚动哈希在O(n+m)的时间内求解:
 假设字符串C = c1c2....cm 定义哈希函数: 
 H(C) = (c1*b^(m-1)+c2*b^(m-2)+.....+cm*b^(0)) mod h  ,   
  其中 b和h是两个互素的数,这样,我们就可以将字符串 S=s1s2...sn 从位置 k+1 开始长度为m的 子串 S[k+1....k+m]的哈希值 
  根据 子串 S[k....k+m-1]的哈希值在常数
 时间内求出来: 
  H(S[k+1....k+m]) = H(S[k..k+m-1]*b - sk*b^m +s(k+m)) mod h ,
只要不断这样右移,就可以在O(n)的时间内求得所有位置的哈希值
#include <iostream>
#include <string>
#include <cstddef>
#include <cstdlib>
using namespace std;
typedef unsigned long long ull;
const ull B = 100000007;

int overlap(string &a,string &b)
{
	int alen = a.size(), blen = b.size();
	int res = 0;
	ull ahash = 0, bhash = 0, t = 1;
	for(int i = 1; i <= min(alen,blen); i++)
	{
		ahash = ahash + (a[alen-i]-'a')*t; //a的长度为i的后缀哈希值
		bhash = bhash*B + b[i-1]-'a';	  //b的长度为i的前缀哈希值
		if(ahash == bhash) res = i;
		t *= B; 
	}
	return res;
} 

int main()
{
	string a,b;
	while(cin>>a>>b)
	{
		cout<<overlap(a,b)<<endl;
	}
	return 0;
}
顺便说一下:不同字符串的哈希值发生冲突的概率非常低,通常可以忽略朴素的检查。

你可能感兴趣的:(字符串匹配,前后缀匹配)