字符串哈希

把任意子串压成一个数,O(1) 回答"这两段一不一样"

一、要解决的问题

字符串题里最常见的原子问题是:"子串 A 和子串 B 相等吗?"逐字符比较是 O(长度),在"比较次数很多"的题目里(回文判断、LCP、循环节)会成为瓶颈。字符串哈希预处理 O(n) 之后,每次比较只要 O(1)。

生活类比:图书馆给每本书编一个索书号。想知道两本书是不是同一本,不用逐页对比——对一下索书号就行。哈希值就是子串的"索书号":号相同几乎必然内容相同。

二、多项式滚动哈希

把字符串看成 B 进制数,前缀哈希递推:

h[0] = 0;h[i] = h[i−1] × B + code(s[i])  (code 把字符映射为非零整数,如 s[i] − 'a' + 1)
子串 s[l..r] 的哈希 = h[r] − h[l−1] × Br−l+1
a b c d e h[1] h[2] h[3] h[4] h[5] 绿色段 "cd" 的哈希 = h[4] − h[2] × B² :把高位部分"减掉",低位正好是 cd 预先存好 B 的各次幂,取任意子串哈希都是 O(1)
前缀哈希:每个前缀是一个 B 进制数,子串哈希 = 高位做差

三、三个高频应用

① O(1) 判断两个子串是否相等

哈希相等即视为相等。配合二分长度可求两个后缀的 LCP(最长公共前缀),O(log n) 每次。

② O(1) 判断子串是否回文

对原串和反转串各建一份哈希:s[l..r] 是回文 ⇔ 原串该段哈希 = 反串对应段哈希(注意下标镜像换算)。与 Manacher 互补:哈希写法短、能答任意区间询问;Manacher 一次求出所有中心。

原串 s: a b c d c b a b 反串 s′: b a b c d c b a "dcb" 是回文 ⇔ 原串红段哈希 = 反串镜像位置红段哈希(两段都是 d→c→b 的"指纹")
回文判定:一段正着读和倒着读的"指纹"相同

③ 循环节 / 周期判断

s 有长度 k 的循环节 ⇔ s[1..n−k] 的哈希 = s[k+1..n] 的哈希,一次哈希比较代替逐位验证。

四、模板代码

typedef unsigned long long ull;
const int N = 1e6 + 10;
const ull B = 131;                    // 或 13331,常用质数进制
ull h[N], p[N];
void init(const char *s, int n) {     // s 从 1 开始存
    p[0] = 1;
    for (int i = 1; i <= n; i++) {
        p[i] = p[i-1] * B;
        h[i] = h[i-1] * B + (s[i] - 'a' + 1);
    }
}
ull get(int l, int r) { return h[r] - h[l-1] * p[r-l+1]; }

用 unsigned long long 自然溢出相当于模 2⁶⁴,省去取模运算;担心被卡可换双模(两个 10⁹ 级质数分别取模)。

五、易错点清单

六、练习

配套小测(5 题,即时判分)