后缀自动机,SAM
后缀自动机,SAM。这玩意可以解决一群字符串问题,但是它本身的原理相当复杂,因此理解这玩意比较困难(10 级考点)。以下基本没有证明。
定义
SAM 可以在线性的空间和时间复杂度内表示给定字符串的所有子串。当然它肯定是自动机,所以来看看它在自动机方面的一些特点。
- SAM 是个 DAG。节点叫做状态,边叫做转移。注意,和PAM不同,每个节点并不只代表一个串。至于具体代表什么一会再说。
- 存在源点
,叫做初始状态,可以到达其他所有点。 - 每个转移标有一些字母,从一个节点出发的所有转移不同。
- 存在一个或多个终止状态。从初始状态出发到达一个终止状态,经过的转移边形成的字符串是原串的一个后缀。
- 在所有满足上述条件的自动机中,SAM 的点数最少。(好像需要 Nerode 定理,如果这一条有知道为啥的老哥评论讲一下谢谢)
具体的一些例子见oiwiki。
两个前置东西
- endpos
对于串
显然,两个字符串的
字符串
的两个非空子串 的 相同,当且仅当 在 中的每次出现都是 的后缀。
显然。
两个非空子串
一定满足:
也很显然。
2. parent 树
当然自动机要有失配指针。SAM的失配指针同样形成一棵树,我们称其为 parent 树,树边称为后缀链接。一个节点
构造
我们可以把原串逐个字符插入 SAM 。我们暂时不标记终止状态,在构造完成之后再标记。
一开始 SAM 只有一个状态
- 令
为添加字符 前整个字符串的状态。 - 创建新状态
,并将 赋值为 。 - 从状态
开始在 parent 树上往上跳。如果这个点没有到字符 的转移,则添加一个到状态 的转移,否则停止,记停止状态为 ,从 通过字符 转移到的状态为 。 - 如果没有找到
,那么直接将 赋值 。 - 如果
,那么 赋值 。 - 否则,创建新状态
,将 除了 以外的所有信息给 ,且 。赋值后使得 。最后从状态 往上跳,只要存在通过 到 的转移,就把它变成到状态 的转移。 - 完成以上过程后将
的值改为 。
如何标记终止状态?整个串
正确性证明
假设加入字符前的 SAM 是正确的,只需要证明操作后的 SAM 仍然正确。
转移边
我们只需要对 4-6 三步对应的三种情况进行讨论。第一种情况的正确性是显然的。二和三的不是那么一眼。
我们尝试向自动机内添加一个已经存在的字符串
否则,转移不连续。我们将状态
最后是重定向转移边的问题。我们也只需要修改相当于所有字符串
复杂度证明
两种实现,一是字符集较大,可以开个 map,时间复杂度多个
考虑算法每个部分,只有三个部分有点问题:
- 上面第 3 步在 parent 树上跳后缀链接。
- 状态
被复制到新状态时复制转移的过程。
这两个本质相同,都是增加转移。而由于 SAM 的转移数是线性的,所以这块的复杂度是线性的。
3. 重定向指向
这一部分不会,留坑。
代码
void ins(char ch){
int p=last;last=++cnt;
len[last]=len[p]+1;
while(p&&!trie[p][ch])trie[p][ch]=cnt,p=fa[p];
if(!p){
fa[last]=1;return;
}
int q=trie[p][ch];
if(len[p]+1==len[q]){
fa[last]=q;return;
}
len[++cnt]=len[p]+1;
for(int j=0;j<26;j++)trie[cnt][j]=trie[q][j];
fa[cnt]=fa[q];fa[q]=cnt;fa[last]=cnt;
while(trie[p][ch]==q)trie[p][ch]=cnt,p=fa[p];
}
初始化
一些性质
状态数
SAM 的状态数不超过
转移数
SAM 的转移边数不超过
分两部分,连续的和不连续的。连续的显然构成一棵树,那么上界
考虑不连续的转移边
此时我们有上界
parent 树的一些性质
称每个前缀对应的节点为终点节点。那么有如下性质:
每个节点的
集合为其子树内所有终点节点。
同时对于每个节点的最长字符串,有:
若节点
是 的祖先,则 对应的字符串是 的后缀。
这也决定了字符串的后缀树就是反串的 parent 树。
应用等我做点题再说,可能短期内不会补。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!