字符串算法的核心是高效比较、匹配和统计子串结构。
你需要建立两个意识:
- 字符串题通常是在处理“相等关系”和“前后缀关系”。
- 很多结构的本质是在复用前缀信息或自动机状态。
把字符串看作某一进制下的数。
设字符串为 (s_1s_2\dots s_n),取底数 (B)、模数 (M),则前缀哈希定义为:
[ H[i] = (H[i-1]\cdot B + val(s_i)) \bmod M ]
子串 ([l,r]) 的哈希值为:
[ hash(l,r)=H[r]-H[l-1]\cdot B^{r-l+1} ]
再对模数取正。
单哈希有碰撞概率。
双哈希使用两个不同模数:
[ (H_1,H_2) ]
可显著降低碰撞风险。
- 判断两个子串是否相等。
- 最长公共前缀二分。
- 回文判断。
KMP 用 next 或 pi 数组维护模式串前后缀匹配信息。
设 pi[i] 表示前缀 s[1..i] 的最长真前后缀长度。
转移核心:
当 (s_{i+1}\ne s_{j+1}) 时,令
[ j = pi[j] ]
直到可以匹配或 (j=0)。
匹配复杂度:
[ O(n+m) ]
z[i] 表示从 i 开始的后缀与整个串的最长公共前缀长度。
复杂度 (O(n))。
适用于:
- 批量求 LCP;
- 扩展 KMP;
- 模式比较。
用于在线性时间内求最长回文子串。
通过在原串中插入分隔符,把奇偶长度回文统一处理。
设 p[i] 表示以 i 为中心的回文半径,则复杂度:
[ O(n) ]
Trie 按字符逐层建树。
适合:
- 字典查询;
- 前缀统计;
- 字符串集合维护。
若字符集大小为 (\Sigma),总长度为 (L),则建树复杂度为:
[ O(L) ]
AC 自动机 = Trie + 失配指针。
失配指针含义:当前匹配失败时跳转到的最长可匹配后缀状态。
其思想与 KMP 类似。
总匹配复杂度通常为:
[ O(\text{文本长度}+\text{模式串总长度}+\text{匹配次数}) ]
fail 树:把每个状态的 fail 指针看作父边,所有状态构成一棵以根为根的树。关键性质:
- 文本中某模式串的出现位置 = 沿 AC 自动机转移到的状态在 fail 树上的祖先链。
因此「每个模式串在文本中出现几次」等价于「fail 树上以该模式串状态为根的子树中,被文本访问过的状态数」。套路:
- 对 fail 树做 DFS 求出
dfn序与子树区间; - 文本每个访问状态在
dfn序上单点 +1; - 子树区间求和即可得每个模式串出现次数。
PAM 又称回文树。
每个结点表示一种本质不同的回文子串。
适合统计:
- 不同回文子串个数;
- 每种回文出现次数;
- 回文相关 DP。
后缀数组是所有后缀排序后的编号数组。
设:
sa[i]:排名第i的后缀起始位置;rk[i]:后缀i的排名;height[i]:sa[i]与sa[i-1]的最长公共前缀。
倍增算法核心思想:
把长度为 (2^k) 的关键字拆为两个长度为 (2^{k-1}) 的块排序。
常见复杂度:
[ O(n\log n) ]
height 数组应用:
height[i] 是排名第 i 与第 i-1 的后缀的最长公共前缀。重要性质:
[ lcp(sa[i], sa[j]) = \min_{i < k \le j} height[k] ]
即任意两名后缀的 LCP 等于它们在 height 数组上对应区间的最小值(可由单调栈 / ST 表维护)。
应用:
- 本质不同子串数:
[ \frac{n(n+1)}{2} - \sum_{i=1}^{n} height[i] ]
- 最长重复子串:
max(height[i])。 - 重复出现至少 k 次的子串:在
height上滑动窗口求长度k-1区间最小值的最大值。 - 两子串 LCP:排名区间 RMQ。
SAM 中每个状态对应一类 endpos 等价类。
常见属性:
len:该状态表示的最长串长度;link:后缀链接。
关键性质:
若状态 (p) 的后缀链接为 (link(p)),则它对应的最短串长度为
[ len(link(p)) + 1 ]
SAM 可用于:
- 不同子串个数;
- 子串出现次数统计;
- 最长公共子串。
不同子串个数公式:
[ \sum_{state} (len[state] - len[link[state]]) ]
广义 SAM 用于多串情形,维护多串的所有子串。
构造方式:
- 每插入一个新串前,把
last重置为根1,再逐字符插入(与单串 SAM 相同); - 或在 Trie 上 BFS,按父状态插入建广义 SAM。
应用:
- 多串本质不同子串数;
- 多串最长公共子串(在每个串对应状态上维护
endpos出现信息,取所有串都出现的最大len); - 多串子串匹配。
注意:直接逐串重置 last 的简单写法在插入时需处理「转移已存在」的情形,避免重复建状态。
- KMP:严格无碰撞,适合精确匹配。
- 哈希:实现灵活,适合子串比较和二分。
- SA 更偏静态排序和后缀区间问题。
- SAM 更偏在线构造和子串统计。
- Trie 处理前缀集合。
- AC 自动机处理多模式串匹配。
- 哈希未统一处理负数。
- KMP
next数组下标混乱。 - AC 自动机构建失配指针后忘记补全转移。
- SA 中
height与rk对应关系写错。 - SAM clone 状态复制不完整。
Luogu P3370【模板】字符串哈希。HDU 1711Number Sequence。Codeforces 432DPrefixes and Suffixes。Luogu P3805【模板】Manacher。
Luogu P2580于是他错误的点名开始了。HDU 2222Keywords Search。Luogu P5496【模板】回文树 / 回文自动机(PAM)。Luogu P5357【模板】AC 自动机(二次加强版 / fail 树)。
Luogu P3809【模板】后缀排序。SPOJ SUBST1New Distinct Substrings。SPOJ LCSLongest Common Substring。Codeforces 235CCyclical Quest。Luogu P6139【模板】广义后缀自动机(广义 SAM)。
推荐顺序:
- 哈希、KMP、Z。
- Manacher。
- Trie、AC 自动机(含 fail 树)。
- SA(含 height 应用)、SAM。
- 广义 SAM、PAM。
模板清单建议至少包括:
- 字符串哈希
- KMP
- Z 函数
- Manacher
- Trie
- AC 自动机(含 fail 树子树统计)
- 后缀数组(含 height)
- 后缀自动机
- 广义 SAM
- 回文自动机