Hash——字符串匹配(求s1在s2中出现的次数)
题目描述:
这是一道模板题。
给定一个字符串 A 和一个字符串 B ,求 B 在 A 中的出现次数。A 和 B中的字符均为英语大写字母。
求A 在 B 中出现了几次。(可重叠)
样例输入:
3
BAPC
BAPC
AZA
AZAZAZA
VERDI
AVERDXIVYERDIAN
样例输出:
1
3
0
首先要知道什么是字符串hash(滚动哈希):
单哈希可以O(m)的时间计算长度为m的字符串的哈希值,但对于本题,总的时间复杂度没有改观。时间会爆。
这时我们就需要一个叫做滚动哈希的优化技巧。
我们选取两个合适的互质常数b和h(b<h),假设字符串C=c1c2……cm,那么我们定义哈希函数:H(C)=(c1bm-1+c2bm-2+……+cmb0) mod h 。
正常数字是十进制的,这里b是基数,相当于把字符串看做是b进制数。
这一过程是递推计算的,设H(C,k)为前k个字符构成的字符串的哈希值,则:(以下均不考虑取模的情况)
H(C,k+1)=H(C,k)× b + ck+1
字符串哈希,通常题目要求的是判断主串的一段字符串与另一个匹配串是否匹配,即判断字符C=c1c2……cm从位置k+1开始的长度为n的子串C'=ck+1ck+2……ck+n的哈希值与另一匹配串S=s1s2……sn的哈希值是否相等,则:
H(C')=H(C,k+n) - H(C,k) × bn
于是我们只要预求得b,就能在O(1)时间内得到任意字符串的字符串的子串哈希值,从而完成字符串匹配,那么上述字符串匹配问题的算法复杂度就为O(n+m)。
在实现算法时,可以利用32位或64位无符号整数计算hash值(如:unsigned long long),并取h=232或h=264,通过自然溢出省去取模运算。
——By《一本通》
那么本题就可以用上述方式AC了(书上代码有bug,需自己改动)
AC代码如下:
#include<cstring>
#include<cstdio> using namespace std; #define ULL unsigned long long #define K 103 int N; char s1[1000005], s2[1000005]; ULL f[1000005],l1,l2,t; ULL a[1000005]; ULL get(int x,int y) { return f[y]-f[x-1]*a[y-x+1]; } int main() { //freopen("字符串匹配(求s1在s2中出现的次数).in","r",stdin); //freopen("字符串匹配(求s1在s2中出现的次数).out","w",stdout); scanf("%d",&N); a[0]=1; for(int i=1;i<=1000000;++i)//预处理出a^n a[i]=a[i-1]*K; for(int i=1;i<=N;++i) { int ans(0);t=0; scanf("%s%s",s2+1,s1+1); l1=strlen(s1+1);l2=strlen(s2+1); for(int j=1;j<=l1;++j) f[j]=f[j-1]*K+(s1[j]-'A');//计算主串的滚动哈希值 for(int j=1;j<=l2;++j) t=t*K+(s2[j]-'A');//计算匹配串的哈希值 for (int j=1;j+l2-1<=l1;++j) { if(get(j,j+l2-1)==t)//枚举起点为i,长度为n的子串,判断与匹配串是否匹配 ans++; } printf("%d\n",ans);//输出 } return 0; }