后缀自动机SAM是高效处理字符串子串问题的数据结构,能在O(n)时间内构建,支持查询子串出现次数、最长公共子串和不同子串个数;其核心由状态节点、转移边、后缀链接组成,通过扩展字符并维护len与link实现,关键步骤包括新建状态、沿后缀链接跳转、判断是否分裂状态以保证最小性;C++实现使用vector存储状态,map处理转移,常见应用有DP统计不同子串总数、反向传播计算出现频率、在SAM上匹配求两串最长公共子串等。

后缀自动机(Suffix Automaton, SAM)是一种高效处理字符串子串问题的数据结构。它能在 O(n) 时间内构建,并支持快速查询所有子串的出现次数、最长公共子串、不同子串个数等问题。C++ 实现 SAM 的核心是理解其状态转移机制和后缀链接(suffix link)。
什么是后缀自动机
SAM 是一个最小化确定性有限自动机,能识别字符串的所有后缀对应的子串。每个状态代表一组具有相同“右端行为”的子串集合。关键组成部分包括:
- 状态节点:包含 len(该状态表示的最长子串长度)、link(后缀链接指向更短的等价类)
- 转移边 trans[c]:字符 c 的转移函数
- last:当前添加字符后的最新状态
构建后缀自动机的步骤
每次向字符串末尾添加一个字符时,扩展自动机结构。主要逻辑如下:
- 新建状态 cur,设置其 len 为 last->len + 1
- 从 last 开始沿后缀链接向上跳,若没有 c 转移,则指向 cur
- 遇到已有 c 转移的节点 p,设 q = p->trans[c]
- 根据 len[q] 是否等于 len[p]+1 分裂状态或直接连接后缀链接
注意:分裂状态是为了保证 SAM 的最小性和正确性。
C++ 实现代码
以下是简洁可运行的 SAM 实现:
```cpp
#include struct State {
int len, link;
map vector void sam_init() {
st.clear();
st.push_back(State());
last = 0;
} void sam_extend(char c) {
int cur = st.size();
st.push_back(State());
st[cur].len = st[last].len + 1;
int p = last; } 例如统计不同子串总数: 基本上就这些。掌握 SAM 关键在于理解状态含义和分裂条件。实现时注意 map 可换成数组(仅限小字符集),提升性能。while (p != -1 && !st[p].trans.count(c)) {
st[p].trans[c] = cur;
p = st[p].link;
}
if (p == -1) {
st[cur].link = 0;
} else {
int q = st[p].trans[c];
if (st[p].len + 1 == st[q].len) {
st[cur].link = q;
} else {
int clone = st.size();
st.push_back(st[q]); // 复制 q 状态
st[clone].len = st[p].len + 1;
while (p != -1 && st[p].trans[c] == q) {
st[p].trans[c] = clone;
p = st[p].link;
}
st[q].link = clone;
st[cur].link = clone;
}
}
last = cur;
<h3>常见应用与操作</h3>
<p>SAM 构建完成后可以解决多种问题:</p>
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











