问题 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;
}
标签:匹配,MAX,unsigned,long,哈希,字符串,看做 From: https://blog.51cto.com/u_16125110/6369246