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 = " # a # b # a # " | — | — | 0 | 0 | C=0, R=0,P 数组全部初始化为 1 | |
| ① | 0 | # | — | 1 | 1 | 0 | 1 | i=0 ≥ R=0 → 跳过镜像;扩展:t[1]='a' vs t[-1]越界 → 停;i+P=1 > R → C=0, R=1 |
| ② | 1 | a | — | 1 | 2 | 1 | 3 | i=1 ≥ R=1 → 跳过镜像;扩展:t[2]='#'=t[0]='#' ✓ → P=2;t[3]='b' vs t[-1]越界 → 停;i+P=3 > R=1 → C=1, R=3 |
| ③ | 2 | # | 0 | 1 | 1 | 1 | 3 | i=2 < R=3 → mirror=2×1−2=0,P[0]=1 → P[2]=min(1,1)=1;扩展:t[3]='b' vs t[1]='a' → ✗ 不匹配,停;i+P=3 ≤ R=3 → 不更新C,R |
| ④ | 3 | b | — | 1 | 4 | 3 | 7 | i=3 ≥ R=3 → 跳过镜像;扩展:t[4]='#'=t[2]='#' ✓ → P=2;t[5]='a'=t[1]='a' ✓ → P=3;t[6]='#'=t[0]='#' ✓ → P=4;t[7]越界 → 停;i+P=7 > R=3 → C=3, R=7 |
| ⑤ | 4 | # | 2 | 1 | 1 | 3 | 7 | i=4 < R=7 → mirror=2×3−4=2,P[2]=1 → P[4]=min(1,3)=1;扩展:t[5]='a' vs t[3]='b' → ✗ 不匹配,停;i+P=5 ≤ R=7 → 不更新 |
| ⑥ | 5 | a | 1 | 2 | 2 | 3 | 7 | i=5 < R=7 → mirror=1,P[1]=2 → P[5]=min(2,2)=2;扩展:t[7]越界 → 停;i+P=7 ≤ R → 不更新 |
| ⑦ | 6 | # | 0 | 1 | 1 | 3 | 7 | i=6 < R=7 → mirror=0,P[0]=1 → P[6]=min(1,1)=1;扩展:t[7]越界 → 停;i+P=7 ≤ R → 不更新 |
| 最终 P = [1, 2, 1, 4, 1, 2, 1],max(P) = 4,答案 = max(P) − 1 = 3(即 "aba") | ||||||||
ios::sync_with_stdio(0);② 忘记 P[i]-1 才是原串回文长度;③ t 的长度是 2n+1,数组要开够。
| 方案 | 暴力匹配 | 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) · 最小生成树 · 拓扑排序