问题 A: 【 哈希和哈希表】子串查找

时间限制: 1 Sec  内存限制: 128 MB
提交: 65  解决: 18
[提交] [状态] [讨论版] [命题人:admin]

题目描述

这是一道模板题。
给定一个字符串A和一个字符串B,求B在A中的出现次数。A和B中的字符均为英语大写字母或小写字母。
A中不同位置出现的B可重叠。

 

输入

输入共两行,分别是字符串A和字符串B。

输出

输出一个整数,表示B在A中的出现次数。

样例输入


zyzyzyz zyz


样例输出


3


提示

1≤A,B的长度≤106,A、B仅包含大小写字母。

[提交][状态]

【哈希与字符串】

对于字符串“521”,也完全可以看做是数字 521,要检查两个数字字符串是不是相同,只需要检测看做数字时是不是相同。若直接字符串比较,时间O(n),若看做数字再比较,时间O(1);

对于字符串“abc”,我们完全可以把a看做1,b看做2.....,不要看做0,避免前导0的影响,对应成一个数字之后,比较时间就从O(n)降到了O(1)!,这就是哈希的魅力啊。这种方式就是把字符串看做一个k进制整数,称为该字符串的哈希值,通过数值比较大小是最快的,但是呢,字符串的长度有时特别长,整型变量难以存储大数,所以我们把它模除M再存储,即我们一般用的哈希值是模除M之后的值。但是这样就不可避免的会有冲突,一定会存在某个哈希值模除M后恰好等于了另一个字符串的哈希值。

对于k和M的选取,大部分文章都强调选择大素数!但是很少给出证明。这里有一篇的观点是尽量大就可以:javascript:void(0) 然后这个知乎回答里好像挺有道理:https://www.zhihu.com/question/20806796/answer/21359160

然后是我个人理解,M越大越好,其次k与M要互质!通过OJ交题,确实是这样才能AC。既然k要与M互质,那直接去素数最好了。有时候可能还会进行除法,就不得不用逆元了,那么M就必须是素数才有逆元。

【分析】

任选一个进制k=97,M=2^64,用unsigned long long 的自然溢出可以完成这个模除。

求出串B的哈希值,然后在A中进行窗口滚动,和每一个长度等于B长度的子串哈希值进行比较,相等的即为字符串相同。

【代码】

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int MAX=1e6+5;

const int base=95;
char a[MAX],b[MAX];
unsigned long long getval(char ch)
{
    if(ch>='a'&&ch<='z')return ch-'a'+1;
    return ch-'A'+27;
}

ull sum[MAX];
int main()
{
    scanf("%s%s",a,b);
    int n=strlen(b),m=strlen(a);
    sum[0]=getval(a[0]);
    for(int i=1;i<m;i++)sum[i]=sum[i-1]*base+getval(a[i]); //前缀值
    ull ha=0,hb=0,f=1;
    for(int i=0;i<n;i++)f=f*base;//当前最大上限
    for(int i=0;i<n;i++)
        ha=(ha*base+getval(b[i]));
    int ans=0;
    for(int i=0;i<m-n+1;i++)
    {
        if(ha==sum[i+n-1]-sum[i-1]*f)ans++;
    }
    printf("%d\n",ans);
    return 0;
}