0%

字符串(一)

字符串 Hash

定义把一个字符串映射到整数的函数 $f$,这个 $f$ 称为是 Hash 函数。

通过这个映射可以判断两个字符串是否相等。

attention:

注意区分哈希和字符串哈希,字符串哈希是哈希问题的一个子集。哈希的另一个经典应用为哈希表。本文介绍字符串哈希。

进制哈希(多项式哈希):

将字符串视作一个 $|c|$ 进制数($|c|$ 表示字符集大小),计算得到的结果即为 $f$ 的值。

优点:

  • 唯一确定,不会产生任何哈希冲突。
  • 每一位字符独立。比如修改字符串中的某一个字符,只需要加减对应权值即可。

缺点:

  • 值域过大,映射的值与字符串长度呈强相关,在字符串长度较长时,存不下这么大的值。

多项式模数哈希(模哈):

即在多项式哈希的基础上,对最后的结果取模。

优点:

  • 可以将字符串映射到 $[0,\bmod)$ 中,方便存储。
  • 模运算具有优秀的性质,可以支持很多其它操作(比如修改字符串中的某一个字符,只需要加减取模即可)。

缺点:

  • 映射范围较小容易产生哈希冲突。

多项式哈希冲突概率:

设模数为 $\bmod$,则映射集合为 $[0,\bmod-1]$。

假设字符串随机分布,即哈希值随机分布(每次从 $[0,\bmod-1]$ 中随机选一个数)。

根据“生日悖论”,在随机 $\sqrt{\bmod}$ 次后,有约 $\dfrac{1}{2}$ 的概率产生冲突。

若产生冲突,则字符串哈希用于“判断两个字符串是否相同”的目的即失效。

所以在随机情况下,$\bmod$ 一般要超过 $\sqrt{\text{字符串数目}}$。

但是上述结论建立在随机的情况下,在实际场景中,往往可以通过对特定模数的特定构造,达到 $2$ 次即冲突的情况。

双模数多项式哈希(双模哈):

即使用两个模数,分别进行多项式模数哈希,只有在两者结果均相同时,认为字符串相同。

同样考虑随机分布下,在随机 $\bmod$ 次后,有约 $\dfrac12$ 的概率产生冲突。

但是这并不能改变哈希冲突的可能性。

构造哈希冲突:

前文中的“生日悖论”概率建立在随机分布意义下,但是由于取模运算本身的性质,哈希冲突并不完全是随机分布的。

考虑:$a\equiv x\pmod c$。

冲突的情况即为同余方程解的数目。

$a\equiv x\pmod c\Leftrightarrow x=a+yc $

若 $\gcd(a,c)\neq 1$,令 $k=\gcd(a,c)$,则 $x=ka’+ykc’\rightarrow \frac{x}{k}=a’+yc’$,即:解空间由 $\lbrace x\rbrace$ 变成了 $\lbrace\dfrac{x}{k}\rbrace$,若原本的冲突空间为 $[0,\bmod-1]$,此时则为 $[0,\dfrac{\bmod-1}{k}]$。

那么为了使冲突空间尽可能大,就要使 $\gcd(a,c)$ 尽可能小,直接让 $\gcd(a,c)=1$ 即可,所以通常选取一个大质数作为模数。

应用:

求一个子串的哈希值:

因为本质上多项式哈希是给每一位字符一个权值然后全部加起来。那么对于一个区间 $[l,r]$ 的权值和,维护一个差分前缀和即可。

一个注意的点是:差分直接得到的是原串中的权值和,并不是一个新的字符串的哈希值(位的权值不一样)。要视作一个新的字符串,则要将权值和除以 ${|c|}^{l}$ 即可。

字符串匹配:

直接判断哈希值是否相等即可。

最长回文子串:

引理:若子串 $sl,r$ 为回文串,则 $s[l-1,r-1]$ 也为回文串。

由引理可得,以回文串中点开始,回文串半径长度满足单调性。

所以可以枚举回文串中点,后二分半径长度,判断左右两部分是否一致即可。

因为左右两部分顺序相反,所以需要对原字符串顺着做一遍哈希后逆着再做一遍哈希。

最长公共前缀(LCP):

最长公共前缀显然也是满足二分性的,所以二分长度后哈希判断是否相同即可。

本质不同回文子串:

一个字符串的本质不同回文子串规模为 $O(n)$(一个容易的证明是回文自动机(PAM)共有 $n$ 个节点)。所以可以使用字符串哈希将所有本质不同的回文子串存下来。

具体方法是:枚举回文串中点时,二分找到最长半径后暴力不断缩小半径,直到遇到出现过的回文子串后终止。