字符串哈希

概念

这个东西很简单,字符串哈希是什么捏?和数字的哈希一样,字符串哈希就是把每一个字符串映射到一个数上去。具体来说,我们可以把一个字符串看成一个大进制数,比如 abc 我们就可以把它看成一个 \(26\) 进制的数字,那么这个数字就是 \(1 \times 26^2 + 2 \times 26 + 3\)。当然有的时候这个数字会很大,一般来说,我们会让它对一个大质数取余。所以我们就可以这样定义一个字符串的哈希函数 \(\operatorname{hash}(str) = (\sum\limits_{i = 0}^n \operatorname{id}(str_i) \times base^{n-i}) \bmod Mod\)。函数 \(\operatorname{id}(str_i)\) 就是 \(str_i\) 的编号,比如说你可以认为 a 的编号是 \(1\) 那么 \(\operatorname{id}(\texttt{a}) = 1\)\(base\) 是我们取的进制,一般取字符集大小,比如说小写字母就取 \(base = 26\),小写大写在一起就 \(base = 52\)。但是有的时候所有字符都在一起,如果你很懒,你也可以直接让 \(base = 137\)\(Mod\) 是一个相当大的质数。

这里有点需要注意:\(\operatorname{id}\) 函数不能有 \(0\) 的值!为什么呢?举个例子,如果我们让 \(\operatorname{id}(\texttt{a}) = 0, \operatorname{id}(\texttt{b}) = 1\),那么 \(\texttt{ab}\)\(\texttt{b}\) 的哈希值就一样了。在这里 \(\operatorname{id} = 0\) 代表这一位是空的。

如果两个字符串的哈希值相同,我们也可以认为他们相同,但是很明显,这是有概率出错的。\(Mod\) 取大素数可以降低出错的概率,因为这个时候我们可以认为哈希值在 \(0\sim Mod-1\) 里均匀分布,所以冲突概率就是 \(\dfrac{1}{Mod - 1}\)。如果 \(Mod\) 不是质数就没有均匀分布了,概率就不能这么算了。一般来讲哈希值用 long long 存,\(Mod\) 可以取成 \(1e9 + 7\) 或是 \(998244353\)。但是这个时候还有可能会冲突,所以我们可以搞两个模数,做算两次哈希,如果两次都一样,我们就可以认为这两个字符串一样。

有的时候我们不方便使用双模数,这个时候我们就可以用伟大的 __int128 弄个 1e18 级别的质数(比如 100000007998244353)


取子串哈希值

光是这样哈希看上去没有啥大用的样子,实际上如果我们算出来一个串的哈希值,我们也可以把它每一个前缀的哈希值给算出来,这很容易,我们记 \(f_i\) 为到第 \(i\) 个字符的哈希值,\(f_i = (f_{i - 1} \times base + \operatorname{id}(str_i))\bmod Mod = (\sum\limits_{j = 0}^{i} \operatorname{id}(str_j)\times base^{i - j})\)。很容易理解。

那么如何取一个子串的哈希值呢?我们记这个子串是 \(str_{l\sim r}\),它的哈希值就应该是 \((\sum\limits_{i =l}^{r} \operatorname{id}(str_i))\bmod Mod \times base^{r - i} = ((f_{r} - f_{l - 1}\times base^{r - l + 1}) \bmod Mod + Mod)) \bmod Mod\),注意这里面 \(f_r - f_{l - 1}\times base^{r - l + 1}\) 可能是负数。


几道题目,我先不放,我以后一起放(

posted @ 2023-07-07 14:12  OIer_SIXIANG  阅读(51)  评论(0编辑  收藏  举报