DAY 02
字符串专题
CSP 暑期集训 · 算法进阶 · 四大核心算法详解
字符串 Hash
进制映射 → 前缀和 → O(1)子串查询
Manacher 马拉车
回文半径 → 镜像加速 → O(n)线性
KMP 算法
next数组 → 最长公共前后缀 → O(n+m)
字典树 Trie
前缀树 → 逐字符插入 → O(|s|)查询
对于每个询问,逐一与所有已知字符串进行逐字符比较。
| 方案 | 暴力比较 | Hash比较 |
|---|---|---|
| 原理 | 逐字符对比,不同即停 | 映射为整数,比较整数 |
| 单次比较 | O(m) 最坏 | O(1) |
| 总时间 | O(n × m) | O(n × m)预处理 + O(n)查询 |
| 子串查询 | 需重新逐字符比较 | 预处理后 O(1) 取任意子串 |
Hash 值可能非常大(B^n 级别),必须取模控制范围。但怎么取模决定了 Hash 的可靠性。
| 对比项 | 方案一:大质数取模 | 方案二:自然溢出(推荐) |
|---|---|---|
| 原理 | mod = 10⁹+7 或 10⁹+9 | 用 unsigned long long,自动 mod 2⁶⁴ |
| 取模方式 | 每次运算后手动 % mod | 不需要手动取模,溢出自动截断 |
| 优点 | 理论安全,冲突率低 | 代码简洁,速度快,无需处理负数 |
| 缺点 | 需处理负数(+mod)%mod,多次取模慢 | 可被特殊数据 hack(生日攻击 2³²) |
| 竞赛推荐 | 被 hack 风险高时(如 CF) | 大多数 OI 比赛首选 |
unsigned long long。
(h[r] - h[l-1]*pw[len]) % mod 可能为负数!((h[r] - h[l-1]*pw[len]) % mod + mod) % mod
| 应用场景 | 描述 | 典型题目 |
|---|---|---|
| 字符串去重 | 判断 n 个字符串中有多少不同 | P3370 |
| 子串比较 | O(1) 判断两个子串是否相同 | 回文判定、重复子串 |
| 二分+Hash | 二分答案 + Hash验证 → O(n log n) | 最长重复子串 |
| 多串匹配 | Hash 判断文本中是否包含某模式 | 简化版字符串匹配 |
| 二维Hash | 扩展到矩阵子图比较 | 图片匹配 |
枚举所有子串 S[i..j](共 O(n²) 个),逐一判断是否回文(O(n)),总计 O(n³)。
Manacher 算法(1975)利用已经计算过的回文信息,避免重复扩展。
核心思想:维护"当前最右回文边界 R",利用镜像关系跳过已知匹配。
| i | t[i] | mirror | P[i]初始 | P[i]最终 | C | R | 操作详解 |
|---|---|---|---|---|---|---|---|
| 初始 | t=全部#交错的 23 长度 | — | — | — | 0 | 0 | C=0,R=0,P 全部初始化为 1 |
| 0 | # | — | 1 | 1 | 0 | 1 | i=0≥R → 跳过镜像;扩展向左越界 → 停;i+P=1>R → C=0,R=1 |
| 1 | a | — | 1 | 2 | 1 | 3 | i=1≥R → 从头扩展:t[2]'#'=t[0]'#' ✓→P=2;左越界→停;C=1,R=3 |
| 2 | # | 0 | 1 | 1 | 1 | 3 | i=2<R → mirror=0,P[0]=1→P=1;扩展 t[3]'b'≠t[1]'a' ✗;不更新 |
| 3 | b | — | 1 | 4 | 3 | 7 | i=3≥R → 扩展:#'a'✓→b✓→#✓ → P=4;左越界→停;C=3,R=7 |
| 4 | # | 2 | 1 | 1 | 3 | 7 | mirror=2,P[2]=1→P=1;扩展 t[5]'a'≠t[3]'b' ✗;不更新 |
| 5 | a | — | 1 | 6 | 5 | 11 | i=5≥R → 扩展:#'a'✓→b✓→#✓→a✓→b?越界→停 → P=6;C=5,R=11 |
| 6 | # | 4 | 1 | 1 | 5 | 11 | mirror=4,P[4]=1→P=1;扩展 t[7]'b'≠t[5]'a' ✗;不更新 |
| 7 | b | 3 | 1 | 4 | 5 | 11 | mirror=3,P[3]=4→但R−i=4→P=min=4;扩展 t[11]'c'≠t[3]'b' ✗;i+P=11≤R 不更新 |
| 8 | # | 2 | 1 | 1 | 5 | 11 | mirror=2,P[2]=1→P=1;扩展 t[9]'a'≠t[7]'b' ✗;不更新 |
| 9 | a | 1 | 1 | 2 | 5 | 11 | mirror=1,P[1]=2→P=2;扩展 t[11]'c'≠t[9]'a' ✗;i+P=11≤R 不更新 |
| 10 | # | 0 | 1 | 1 | 5 | 11 | mirror=0,P[0]=1→P=1;扩展 t[11]'c'≠t[9]'a' ✗;不更新 |
| 11 | c | — | 1 | 10 | 11 | 21 | i=11≥R=11 → 从头扩展,连续匹配9层到 P=10!越界→停;C=11,R=21(超大中心诞生) |
mirror 取值即终值、要么截断到 R(17 举例),只有一次扩展就停。这就是 O(n) 的关键:R 每轮最多右移一步,总共只移动 ≈ n 次;而"从头逐字符比较"的大扩展全被镜像省掉。
ios::sync_with_stdio(0);② 忘记 P[i]-1 才是原串回文长度;③ t 的长度是 2n+1,数组要开够。
[n−1−r, n−1−l] 的 l、r 顺序是反的(右端用 l、左端用 r)。照抄 [l,r] 会得到错误的反向哈希,整题全错。建议先写出具体下标验一次再上机。
| 类型 | 中心 | 判回文区间 | 真实长度 |
|---|---|---|---|
| 奇数长度 | 单字符 i(如 "aba" 以 b 为中心) | [i−m, i+m] | 2m+1 |
| 偶数长度 | 两字符 i、i+1 之间(如 "abba") | [i−m+1, i+m] | 2m |
odd(i):以 i 为轴往外扩 m;偶数中心用 even(i):轴在 i 与 i+1 之间。h 建 正向前缀哈希、rh 建 反转串 rs 的前缀哈希、pw 存 B 的幂。sub(H,l,r) → 取 H 数组中的子串哈希 H[r+1]−H[l]·pw[r−l+1]。isPal(l,r):正向取 sub(h,l,r),反向取 sub(rh, n−1−r, n−1−l)(镜像区间,方向与正向对齐),相等即回文。odd(i) 以 i 为轴二分半径、even(i) 以 i,i+1 之间为轴二分——对应思路页"奇偶两种中心"。
| 维度 | Manacher | Hash + 二分(备份) |
|---|---|---|
| 时间复杂度 | O(n) 线性 | O(n log n) |
| 实现难度 | 较难(镜像加速易错) | 简单(只判回文) |
| 正确性风险 | 无哈希碰撞 | 哈希碰撞(用双哈希压到近0) |
| 适用 | 模板题 / 超大 n | 普通数据 / 临场快速兜底 |
| 方案 | 暴力匹配 | KMP | 优化点 |
|---|---|---|---|
| 失配后 | i回退,j归零 | i不回退,j=next[j] | 利用已匹配信息 |
| 时间复杂度 | O(n × m) | O(n + m) | 线性! |
while(j>0 && ...!=P[j]) j=nxt[j-1] 完全相同!
if (j == m) { pos.push_back(...); j = nxt[j-1]; }
| 对比项 | KMP | 字符串 Hash |
|---|---|---|
| 适用场景 | 精确找模式串所有出现位置 | 快速判断两个子串是否相同 |
| 时间复杂度 | O(n + m) | O(n)预处理 + O(1)查询 |
| 空间复杂度 | O(m) | O(n) |
| 优点 | 精确、无碰撞风险 | 灵活,支持任意子串比较 |
| 缺点 | 只能匹配一个模式 | 有碰撞风险(双Hash可解) |
| 典型题目 | P3375 模式匹配 | P3370 字符串去重 |
| 应用场景 | 描述 | 典型题目 |
|---|---|---|
| 字符串存在性查询 | 判断一个字符串是否在集合中 | P2580 名字拼写 |
| 前缀查询 | 统计以某前缀开头的字符串个数 | 自动补全 |
| 最大异或对 | 将数字转为二进制串,Trie上贪心 | 最大XOR |
| AC自动机基础 | Trie + KMP fail → 多模式匹配 | Day03 预告 |
| 01Trie | 处理二进制/位运算问题 | 区间异或最值 |
| 算法 | 解决的问题 | 时间复杂度 | 核心思想 |
|---|---|---|---|
| Hash | 快速比较子串 | O(n)预处理+O(1)查询 | 进制映射→指纹 |
| Manacher | 最长回文子串 | O(n) | 镜像加速 |
| KMP | 模式匹配 | O(n+m) | next数组避免回退 |
| Trie | 字符串集合查询 | O(|s|)每次 | 前缀树共享路径 |
| 编号 | 题目 | 知识点 | 难度 |
|---|---|---|---|
| 1 | P1210 [USACO1.3] 最长的回文 Calf Flac | Manacher 入门 | 普及 |
| 2 | P9606 [CERC2019] ABB | 最长回文后缀(KMP/Manacher) | 普及+/提高− |
| 3 | P1872 回文串计数 | Hash + DP | 普及+/提高− |
| 4 | P4391 [BOI2009] Radio Transmission 无线传输 | KMP 循环节 | 普及+/提高− |
| 5 | P11276 第一首歌 | KMP border | 普及+/提高− |
| 6 | P10470 前缀统计 | Trie | 普及+/提高− |
| 7 | P15835 [蓝桥杯第一届国际赛] 基因配对 | KMP | 普及+/提高− |
| 8 | P1659 [国家集训队] 拉拉队排练 | Manacher + 快速幂 | 提高 |
| 9 | P2353 背单词 | KMP + 区间查询 | 提高 |
| 10 | P8085 [COCI 2011/2012 #4] KRIPTOGRAM | Hash + KMP | 提高 |
📌 字符串 Hash:进制映射 → 前缀和 → O(1) 子串查询
📌 Manacher:插入'#' 统一奇偶 → 镜像加速 → O(n) 线性
📌 KMP:next 数组 = 最长公共前后缀 → j=next[j-1] → O(n+m)
📌 Trie:前缀树 → 边是字符路径是串 → O(|s|) 查询
🎯 明日预告:Day 03 · 图论基础
最短路算法(Dijkstra / Floyd) · 最小生成树 · 拓扑排序