题目描述


分析:

字符串 hash 小试牛刀

我们在之前模拟散列时,设置的哈希函数为将一个元素(element, e)输入哈希函数中,输出是一个整数,而那时的 e e e 为一个有范围的整数。现在我们考虑更复杂的情形, e e e 为一个字符串,为了区分,记为 E E E,字符串哈希函数就是将一个字符串 E E E 映射为一个整数,使得该整数可以尽可能地代表一个字符串 E E E。
假设字符串 E E E 由大写字母 A − Z A-Z A−Z 和小写字母 a − z a-z a−z 构成,在这个基础上,我们可以对字母进行编码:利用 c o n v e r t ( ) convert() convert() 函数,将 A − Z A-Z A−Z 为 1 − 26 1-26 1−26, a − z a-z a−z 为 27 − 52 27-52 27−52,举个例子 c o n v e r t ( D ) = 4 convert(D) = 4 convert(D)=4。这样就把大小写字母对应到了五十三进制中。接着,按照将五十三进制转换为十进制的思路,由进制转换的结论可知,在进制转换中,得到的十进制是唯一的,由此便可将字符串映射成整数的需求。这里转换成的整数最大为 5 3 l e n g t h − 1 53^{length}-1 53length−1(进制转换的基本性质), l e n g t h length length 为字符串的长度。实现代码如下:

// 哈希函数 h() 本质即为进制转化
int h(char* E)
{int ans = 0;for (int i = 0; E[i]; i ++){if (E[i] >= 'A' && E[i] <= 'Z') ans = ans * 53 + (E[i] - 'A');else if (E[i] >= 'a' && E[i] <= 'z') ans = ans * 53 + (E[i] - 'a') + 26;}return ans;
}

假设字符串为"BCDE",进行哈希映射后得到的整数为 5 ∗ 5 3 0 + 4 ∗ 5 3 1 + 3 ∗ 5 3 2 + 2 ∗ 5 3 3 5*53^0+4*53^1+3*53^2+2*53^3 5∗530+4∗531+3∗532+2∗533。从代码和样例中可以看出为什么我们在之前将 A − Z A-Z A−Z 设为 1 − 26 1-26 1−26 而不是 0 − 25 0-25 0−25,这是因为如果 “A” 为 0 0 0,那么 “AA”、“AAA” 都为 0 0 0 了。

字符串 hash 进阶

对于上面的分析,我们可以总结出一个更系统的式子:

S [ i ] = S [ i − 1 ] ∗ p + E [ i ] S[i]=S[i-1]*p+E[i] S[i]=S[i−1]∗p+E[i]

其中 p p p 为我们所采取的进制, E [ i ] E[i] E[i] 表示字符串的第 i i i 位是什么, S [ i ] S[i] S[i] 表示字符串的前 i i i 个字符的子串的 hash 值。这样,当 i i i 取遍 1 − l e n g t h 1-length 1−length 后, S [ l e n g t h ] S[length] S[length] 就是整个字符串的哈希值,而其他位置保存了部分字串的 hash 值。注意这里没有了 c o n v e r t ( ) convert() convert() 函数,因为我们打算直接是用字符串字符的 ASCII 码 ,因此 p p p 进制的选择就扑朔迷离了。
在转换过程中,字符串与整数是一一对应的,但由于没有适当的处理,当字符串字符较长时,产生的数会非常大,没办法用一般的数据类型存储。因此,按照之前对哈希的理解,要进行取模,即:

S [ i ] = ( S [ i − 1 ] ∗ p + E [ i ] ) % k S[i]=(S[i-1]*p+E[i]) \%k S[i]=(S[i−1]∗p+E[i])%k

通过这种方式可以把字符串转换成范围上能接受的整数。但这可能又产生另外的问题,也就是 hash值产生冲突。而根据y总的经验值, p p p 取 133 133 133 或 13331 13331 13331, k k k 取 2 64 2^{64} 264,在 99.99 % 99.99 \% 99.99% 的情况下是不会产生冲突的。因此这和普通哈希是有区别的,普通哈希是可以处理冲突的,而字符串哈希是不考虑冲突的(无法解决?)

子串的 hash值

考虑求解子串的 hash值,也就是求解 S [ l . . . r ] S[l...r] S[l...r]。由于上面介绍的取模运算在括号最外层,而我们接下来需要考虑括号内的问题,所以先暂时把取模运算放在一边,简化讨论。即:

S [ i ] = ( S [ i − 1 ] ∗ p + E [ i ] ) S[i]=(S[i-1]*p+E[i]) S[i]=(S[i−1]∗p+E[i])

子串的 hash值, S [ l . . . r ] S[l...r] S[l...r] 实际上等于把字符串 E [ l . . . r ] E[l...r] E[l...r] 从 p p p 进制转换为十进制,也就是如下式子所表示的:

S [ l . . . r ] = E [ l ] ∗ p r − l + E [ l + 1 ] ∗ p r − l − 1 + . . . + E [ r ] ∗ p 0 S[l...r]=E[l]*p^{r-l}+E[l+1]*p^{r-l-1}+...+E[r]*p^0 S[l...r]=E[l]∗pr−l+E[l+1]∗pr−l−1+...+E[r]∗p0

看到这个式子有没有觉得头大呢?没关系,这是很正常的,因为它有许多的参数需要理清,先看一个实例:

上面的 S [ l . . . r ] S[l...r] S[l...r] 的公式可以通过 S [ j ] S[j] S[j] 推导出:

S [ r ] = S [ r − 1 ] ∗ p + E [ r ] S[r]=S[r-1]*p+E[r] S[r]=S[r−1]∗p+E[r]
= ( S [ r − 2 ] ∗ p + E [ r − 1 ] ) ∗ p + E [ r ] \qquad =(S[r-2]*p+E[r-1])*p+E[r] =(S[r−2]∗p+E[r−1])∗p+E[r]
= S [ r − 2 ] ∗ p 2 + E [ r − 1 ] ∗ p + E [ r ] \qquad =S[r-2]*p^2+E[r-1]*p+E[r] =S[r−2]∗p2+E[r−1]∗p+E[r]
= . . . \qquad = ... =...
= S [ l − 1 ] ∗ p r − l + 1 + E [ l ] ∗ p r − l + . . . + E [ r ] ∗ p 0 \qquad = S[l-1]*p^{r-l+1}+E[l]*p^{r-l}+...+E[r]*p^0 =S[l−1]∗pr−l+1+E[l]∗pr−l+...+E[r]∗p0
= S [ l − 1 ] ∗ p r − l + 1 + S [ l . . . r ] \qquad = S[l-1]*p^{r-l+1}+S[l...r] =S[l−1]∗pr−l+1+S[l...r]

移项可以得到:

S [ l . . . r ] = S [ r ] − S [ l − 1 ] ∗ p r − l + 1 S[l...r]=S[r]-S[l-1]*p^{r-l+1} S[l...r]=S[r]−S[l−1]∗pr−l+1

于是就得到了子串 E [ i . . . j ] E[i...j] E[i...j] 的 hash值 S [ i . . . j ] S[i...j] S[i...j],加上原来的取模操作可以得到:

S [ l . . . r ] = ( S [ r ] − S [ l − 1 ] ∗ p r − l + 1 ) % k S[l...r]=(S[r]-S[l-1]*p^{r-l+1})\%k S[l...r]=(S[r]−S[l−1]∗pr−l+1)%k

由于C++有 unsigned long long(ULL) 类型表示范围为 [ 0 , 2 64 − 1 ] [0,2^{64}-1] [0,264−1],因此当需要对 2 64 2^{64} 264 取模时,可以用 ULL 类型存储该数,当出现溢出时就相当于进行了取模运算


代码(C++)

#include <iostream>using namespace std;typedef unsigned long long ULL;const int N = 100010, P = 131;
char E[N];
// POW 数组进行提前打表操作,i 中记录了 p^i
ULL H[N], POW[N];//返回值为 ULL,相当于进行了取模运算
ULL subhash(int l, int r)
{// P 的 r-l+1 次幂,存储在对应下标中return H[r] - H[l - 1] * POW[r - l + 1];
}int main()
{int n, m;// 对于字符串 E,从下标为 1 的位置读入scanf("%d%d%s", &n, &m, E + 1);POW[0] = 1;for (int i = 1; i <= n; i ++){// H[i] 表示字符串前 i 个字符的子串 hash值H[i] = H[i - 1] * P + E[i];// 打表幂运算POW[i] = POW[i - 1] * P;}while (m --){int l1, r1, l2, r2;scanf("%d%d%d%d", &l1, &r1, &l2, &r2);if (subhash(l1, r1) == subhash(l2, r2)) puts("Yes");else puts("No");}return 0;
}

代码(Python3):

def sub(l, r):return (H[r] - H[l - 1] * POW[r - l + 1]) % (2 ** 64)if __name__ == '__main__':n, m = map(int, input().split())N, P = 100010, 131E = input()H = [0] * NPOW = [0] * NPOW[0] = 1for i in range(len(E)):H[i + 1] = (H[i] * P + ord(E[i])) % (2 ** 64)POW[i + 1] = (POW[i] * P) % (2 ** 64)for _ in range(m):l1, r1, l2, r2 = map(int, input().split())if sub(l1, r1) == sub(l2, r2):print('Yes')else:print('No')

AcWing 841. 字符串哈希相关推荐

  1. ~~字符串哈希(数据结构)(附模板题AcWing 841 字符串哈希)

    核心思想: 将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低. 小技巧: 取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果. ...

  2. AcWing 841. 字符串哈希(字符串Hash)

    题目连接 https://www.acwing.com/problem/content/843/ 思路 我们用一个数组a记录改字符串的前缀hash值,然后和前缀和类似的方法,不过注意的是,我们在计算区 ...

  3. Hash(哈希(字符串哈希))模板和做题总结(详细易懂)

    文章目录 目录 文章目录 前言: 一 Hash表 1 Hash函数的构造 2 拉链法处理hash冲突模板 3 开放寻址法处理hash冲突 4(例题).雪花雪花 二   字符串Hash O(n)+O(m ...

  4. 字符串处理之---字符串哈希

    前言 字符串哈希,非常非常好用NB的方法,虽然有一定概率会翻车(翻车概率极低),但是这个是真的NB,你如果会这个在大多数场合可以避免掉有一些算法的学习,比如马拉车算法, 你学马拉车只能处理回文字符串, ...

  5. 字符串哈希算法简单入门学习

    字符串哈希算法 字符串哈希,最著名的就是BKDRHash,也就是将字符串变成数值,并且最后变成的数值是一个P进制的数(一班取131或者13331),一般来说P最好为素数.然后我们之所以需要前缀和,是因 ...

  6. ELFhash - 优秀的字符串哈希算法

    原 ELFhash - 优秀的字符串哈希算法 分类:算法杂论算法精讲数据结构 (1424)  (2) 1.字符串哈希: 我们先从字符串哈希说起 在很多的情况下,我们有可能会获得大量的字符串,每个字符串 ...

  7. 【CodeForces】961 F. k-substrings 字符串哈希+二分

    [题目]F. k-substrings [题意]给定长度为n的串S,对于S的每个k-子串$s_ks_{k+1}...s_{n-k+1},k\in[1,\left \lceil \frac{n}{2} ...

  8. 138. 兔子与兔子【字符串哈希】

    很基础的字符串哈希 #include<bits/stdc++.h> using namespace std; typedef unsigned long long int ull; con ...

  9. Singing Superstar 字符串哈希-map操作

    题意 : 给一长度 < 1e5 的字符串s,q < 1e5次询问,每次问一个长 < 30 的串 t 在s中出现的次数,且t不可重叠. 例 : "abababa"中 ...

最新文章

  1. Centos 7环境下源码安装PostgreSQL数据库
  2. 如何获取并操作listview中的控件
  3. Spring任务调度
  4. python爬取评论_python爬取网易云音乐评论
  5. 开发者新春回血大礼包助你2021畅行无压力!
  6. 周小鹏:努力让FPGA支持更多开源库和框架
  7. 关于spark structed stream 流中的触发trigger间隔的理解
  8. rdbms mysql_不同RDBMS下Join 用法 - MySQL
  9. QQ空间相册批量下载qq相册批量下载专家比骨头小林还厉害
  10. win7快捷方式去箭头_win7旗舰版桌面图标快捷方式箭头变成黑块怎么办?
  11. 什么软件能测试显卡功耗,测试方案及测试平台
  12. python处理页眉_python批量替换页眉页脚实例代码
  13. 分享一些可用的淘宝(1688)关于订单信息获取的相关接口(开放API收费)
  14. call forwarding
  15. vue中处理后台返回的 html 特殊标签(‘\lt; p style=“xxx“ \gt;‘)或(\<p>)的三种情况及传给后端数据的解决方案
  16. 输入一个百分制成绩,要求输出成绩等级A、B、C、D、E,其中90-100分为A,80-89分为B,70-79分为C,60-69分为D,60分以下为E。
  17. OpenGL(十四)——Qt OpenGL纹理
  18. 如何用Python从海量文本抽取主题
  19. 电子制造企业如何减少不必要的跨部门沟通,快速回复订单交期?
  20. Linux中防火墙firewalld

热门文章

  1. CocosCreator项目实战(14):功能-分享
  2. 关于string头文件
  3. 分布式系统架构系列讲解三(分布式一致性 3):共识问题
  4. 02 Vue进阶 render和jsx语法使用
  5. 易经玄学诠释人的一生
  6. 洛谷p5738 歌唱比赛c++题解
  7. matplotlib的hist函数绘制直方图
  8. 仿真(simulation)与模拟(emulation)的区别
  9. 使用TASM编译COFF格式和连接
  10. kali创建root用户