字符串哈希
创始人
2024-02-07 03:21:35

目录

  • 前言
        • 解决hash冲突
        • 并不真正地使用%Q\%Q%Q
  • 字符串哈希 #1
    • Code
  • 洛谷 P3370 - 字符串哈希 #2
    • Code
  • 总结

前言

字符串哈希号称是学了后再遇到字符串题就可以丢掉kmp的算法哈哈(当然bushi)。

它是一种将字符串映射成数字的算法,如何映射的呢?
利用的就是hash,其方式是将一个给定的字符串转成 PPP进制的数字,即对字符串每个位置iii上的字符赋予一个权值PiP^iPi(这个权值从左到右依次递减),然后用一个数字取替该字符(一般来讲用的是字符对应ASCII码值)并乘上权值后相加起来,可以预想到此时的值会非常地庞大,所以再给它mod上一个限定值QQQ。
用公式表示就是:

字符串 —> s1s2s3s4......sn−1sns_1s_2s_3s_4......s_{n-1}s_ns1​s2​s3​s4​......sn−1​sn​。
哈希值—>(s1×Pn−1+s2×Pn−2+s3×Pn−3+......sn−1×P1+sn×P0)%Q(s_1×P^{n-1}+s_2×P^{n-2}+s_3×P^{n-3}+......s_{n-1}×P^1+s_n×P^0) \% Q(s1​×Pn−1+s2​×Pn−2+s3​×Pn−3+......sn−1​×P1+sn​×P0)%Q。

如果让h[i]存字符串s[1...i]s[1...i]s[1...i]的hash值,也即整个字符串的hash值存在h[n]里面;
代入上述公式有:

h[1]=s[1]h[1] = s[1]h[1]=s[1],
h[2]=s[1]×P+s[2]h[2] = s[1]×P+s[2]h[2]=s[1]×P+s[2]
h[3]=s[1]×P2+s[2]×P+s[3]h[3] = s[1]×P^2+s[2]×P+s[3]h[3]=s[1]×P2+s[2]×P+s[3]
h[4]=s[1]×P3+s[2]×P2+s[3]×P+s[4]h[4] = s[1]×P^3+s[2]×P^2+s[3]×P+s[4]h[4]=s[1]×P3+s[2]×P2+s[3]×P+s[4]
… …
(先不显示%Q\%Q%Q)
由此,就可以得到一个递推式:h[i]=h[i−1]×P+s[i],i∈[1,n]h[i] = h[i-1]×P+s[i],i∈[1,n]h[i]=h[i−1]×P+s[i],i∈[1,n]。

那么观察一下就可以发现递推式有点点像一个前缀和公式,其实也就说明了可以从111到nnn递推地求出个各个h[]值,不仅如此,进一步拓展还可以再求字符串某个子串的hash值:

例如,子串s[2..4]s[2..4]s[2..4]的hash值=s[2]×P2+s[3]×P+s[4]s[2]×P^2+s[3]×P+s[4]s[2]×P2+s[3]×P+s[4],同时,也=h[4]−h[2−1]×P4−2+1h[4]-h[2 -1]×P^{4-2+1}h[4]−h[2−1]×P4−2+1,因此还得到一个区间和的公式:h[l,r]=h[r]−h[l−1]×Pr−l+1h[l,r]=h[r]-h[l-1]×P^{r-l+1}h[l,r]=h[r]−h[l−1]×Pr−l+1。


解决hash冲突

有了映射,那么有个值得注意的问题就是如何解决hash冲突(这里是hash值相等的情况)。

  • 首先,每个代表字符的数字不能为0,举个例子,若用0代替字符'A',那么对于字符串"A""AAA""AAAAAA"来讲,它们的hash值均为0,这就引起冲突大了!所以要以非0的数字代替字符以区别开。
  • 由经验之谈,PPP一般设置成131,QQQ设置成2642^{64}264时,各种字符串映射出的hash值不相等的概率为99.99%99.99\%99.99%(据说),那么此时就可以近似地认为不产生冲突

有了上述定义后,思考一个问题,如何比较两个字符串是否相等,那就简单了,先将它们都进行hash映射,然后对其hash值进行比较是否相等作为依据就可以了,由于是整数上的比较,那么该操作的复杂度仅仅只有O(1)O(1)O(1)。


并不真正地使用%Q\%Q%Q

一般来讲,用C++写的话并不需要在每次计算hash值时%Q\%Q%Q,转而可以考虑使用unsigned long long(ULL)数据类型来存,用这种数据类型有啥好处呢?一是存的数值比较大,适合拿来映射大数据量的字符串,二是当一个ULL的数溢出时,编译器自动会将这个数进行%264\%2^{64}%264处理,这正好是跟QQQ设置成2642^{64}264时期望吻合的啊。

有关更多字符串哈希的数学解释以及详细定义还可参考:字符串哈希 - OI Wiki。


字符串哈希 #1

给定一个长度为 n 的字符串,再给定 mmm 个询问,每个询问包含四个整数 l1,r1,l2,r2l_1,r_1,l_2,r_2l1​,r1​,l2​,r2​,请你判断 [l1,r1][l_1,r_1][l1​,r1​] 和 [l2,r2][l_2,r_2][l2​,r2​] 这两个区间所包含的字符串子串是否完全相同。

字符串中只包含大小写英文字母和数字。

输入格式
第一行包含整数 nnn 和 mmm,表示字符串长度和询问次数。

第二行包含一个长度为 nnn 的字符串,字符串中只包含大小写英文字母和数字。

接下来 mmm 行,每行包含四个整数 l1,r1,l2,r2l_1,r_1,l_2,r_2l1​,r1​,l2​,r2​,表示一次询问所涉及的两个区间。

注意,字符串的位置从 111 开始编号。

输出格式
对于每个询问输出一个结果,如果两个字符串子串完全相同则输出 Yes,否则输出 No

每个结果占一行。

数据范围
1≤n,m≤1051≤n,m≤10^51≤n,m≤105

输入样例:

8 3
aabbaabb
1 3 5 7
1 3 6 8
1 2 1 2

输出样例:

Yes
No
Yes

Code

#include 
#include 
using namespace std;
typedef unsigned long long ULL;const int N = 1e5 + 10, P = 131;ULL h[N];
ULL p[N];       //p[i]其实就是存P的i次方
char s[N];
int n, m;ULL get(int l, int r){      //子串s[l...r]的hash值return h[r] - h[l - 1] * p[r - l + 1];
}int main(){ios :: sync_with_stdio(false);cin >> n >> m;cin >> s + 1;p[0] = 1;//预处理hash值for(int i = 1;i <= n;i ++){h[i] = h[i - 1] * P + s[i];p[i] = p[i - 1] * P;}while(m --){int l1, r1, l2, r2;cin >> l1 >> r1 >> l2 >> r2;int h1 = get(l1, r1), h2 = get(l2, r2);if(h1 == h2)        cout << "Yes" << endl;else                cout << "No" << endl;}return 0;
}

洛谷 P3370 - 字符串哈希 #2

如题,给定 NNN 个字符串(第 iii 个字符串长度为 MiM_iMi​,字符串内包含数字、大小写字母,大小写敏感),请求出 NNN 个字符串中共有多少个不同的字符串。

友情提醒:如果真的想好好练习哈希的话,请自觉,否则请右转PJ试炼场:)

输入格式

第一行包含一个整数 NNN,为字符串的个数。

接下来 NNN 行每行包含一个字符串,为所提供的字符串。

输出格式

输出包含一行,包含一个整数,为不同的字符串个数。

样例 #1

样例输入 #1

5
abc
aaaa
abc
abcc
12345

样例输出 #1

4

提示

对于 30%30\%30% 的数据:N≤10N\leq 10N≤10,Mi≈6M_i≈6Mi​≈6,Mmax≤15Mmax\leq 15Mmax≤15。

对于 70%70\%70% 的数据:N≤1000N\leq 1000N≤1000,Mi≈100M_i≈100Mi​≈100,Mmax≤150Mmax\leq 150Mmax≤150。

对于 100%100\%100% 的数据:N≤10000N\leq 10000N≤10000,Mi≈1000M_i≈1000Mi​≈1000,Mmax≤1500Mmax\leq 1500Mmax≤1500。

样例说明:

样例中第一个字符串(abc)和第三个字符串(abc)是一样的,所以所提供字符串的集合为{aaaa,abc,abcc,12345},故共计4个不同的字符串。


Code

#include 
#include 
#include 
using namespace std;
typedef unsigned long long ULL;const int N = 1e5 + 10, P = 131;ULL hs[N];      //存字符串的hash值
int n;
char s[1510];ULL get(){int len = strlen(s + 1);int h = 0, p = 1;for(int i = 1;i <= len;i ++){h = h * p + s[i];p *= P;}return h;
}int main(){ios :: sync_with_stdio(false);cin >> n;for(int i = 0;i < n;i ++){cin >> s + 1;hs[i] = get();}sort(hs, hs + n);int ans = 1;for(int i = 1;i < n;i ++){if(hs[i] != hs[i - 1])      ans ++;}cout << ans << endl;return 0;
}

总结

可以看到,经过一遍hash值的预处理后,给后续子串之间的查询操作带来了极大的方便,让原本繁琐的字符串匹配转变成了只是数字上的比较。

相关内容

热门资讯

北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...