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,需自己改动)

  代码如下:

#include<cstdio>
#include<cstring>
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;
}

猜你喜欢

转载自www.cnblogs.com/lck-lck/p/9588740.html