字符串哈希 详解+例题
字符串哈希
update 2024,10,6 添加一道题
观看讲解视频:董晓算法 做的笔记
理论部分
字符串哈希 是把不同的字符串映射成不同的整数。
-
对于一个长度为 \(n\) 的字符串 \(s\),我们定义它的 Hash 函数为:
\(h(s) = \Sigma ^n_{i=1}\) \(s[i] \times p^{n-i}\) \((mod\) \(m)\)
例如:字符串 \(abc\),他的 hash 函数值为 \(ap^2+bp^1+cp^0\)
即 \(97\times131^2+98\times131^1+99\) -
如果两个字符串不一样,Hash 值却一样,这样的现象称为 哈希碰撞
-
解决哈希碰撞的方法:
保证 \(p\)、\(m\) 互质,\(p\) 通常取质数 \(131\) 或 \(13331\);\(m\) 通常取大整数 \(2^64\),哈希函数要定义为 ULL(unsigned long long),超过则自动溢出,等价于取模。
关于 unsigned long long:无符号,取值范围在 \(0 \sim 2^{64}\)
代码实现:
- 求一个字符串的哈希值:前缀和,
前缀和:\(h[i] = h[i-1] \times p + s[i]\),\(h[0] = 0\) (\(h[i]\) 表示前 \(i\) 项的 hash 值)
时间复杂度:\(O(n)\) - 他的子串的哈希值就是区间和
区间和:\(h[l,r] = h[r] - h[l] \times p^{r-l+1}\)
时间复杂度:\(O(1)\)
例题:
P3370 【模板】字符串哈希
模板题,直接上代码。
#include<bits/stdc++.h>
using namespace std;
#define ULL unsigned long long
const int maxn=100010;
const int p=131;
int n;
int lens;
char s[maxn];
ULL ans[maxn],h[maxn];
int cnt=0;
ULL calc(char s[],int n)
{
h[0]=0;
for(int i=1;i<=lens;i++)
{
h[i]=h[i-1]*p+s[i]; //求 hash 值,前缀和,便于求区间 hash 值(虽然这题不需要 QWQ )
}
return h[lens];
}
//计算哈希值
int main(){
cin>>n;
for(int i=1;i<=n;i++)
{
scanf("%s",s+1);
lens=strlen(s+1);
ans[i]=calc(s,lens);//计算哈希值
}
sort(ans+1,ans+1+n);
for(int i=1;i<=n;i++)
{
if(ans[i]!=ans[i-1])cnt++;
}
cout<<cnt<<endl;
return 0;
}
P2957 [USACO09OCT] Barn Echoes G
- 题目大意:求两个字符串s1 s2的重复部分
两个字符串的重复部份指的是同时是一个字符串的前缀和另一个字符串的后缀的字符串
- 思路:
计算hash值,枚举重复部分的长度,计算区间hash值相等的最长长度。
虽然这道题暴力也可以,但是好不容易碰见一道能用hash的题 - 代码:
//30ms直接拿下!
#include<bits/stdc++.h>
using namespace std;
#define ULL unsigned long long
const int p=131;
char s1[100],s2[100];
ULL h[100][5],pow_p[100];
int ans=0;
ULL get_valu(int l,int r,int cnt) //求区间hash值
{
return h[r][cnt]-h[l-1][cnt]*pow_p[r-l+1];
}
void Hash(char t[],int cnt)//hash函数
{
h[0][cnt]=0;
int len=strlen(t+1);
for(int i=1;i<=len;i++)
{
h[i][cnt]=h[i-1][cnt]*p+t[i];
}
}
int main()
{
scanf("%s%s",s1+1,s2+1);
Hash(s1,1);//分别求出hash值
Hash(s2,2);
pow_p[0]=1;
for(int i=1;i<=100;i++)
pow_p[i]=pow_p[i-1]*p;//直接预处理一下p的次方
int len1=strlen(s1+1);
int len2=strlen(s2+1);
int maxn=min(len1,len2);
for(int i=maxn;i>=1;i--)
{
int nums1_big=get_valu(1,i,1),nums1_ed=get_valu(len1-i+1,len1,1),
nums2_big=get_valu(1,i,2),nums2_ed=get_valu(len2-i+1,len2,2);
//求长度为 i 的 s1 前缀hash值、后缀hash值 和 s2 的前缀hash值、后缀hash值
// cout<<nums1_big<<" "<<nums1_ed<<" "<<nums2_big<<" "<<nums2_ed<<endl;调试
if(nums1_big==nums2_ed||nums2_big==nums1_ed)
{
cout<<i<<endl;
return 0;
}
}
}