SAM(后缀自动机)
SAM
之前听 yxc 讲的时候没有细致的讲是如何构造的,今天难得听到了 yny 讲的 SAM,真的很细致!(当然我可能写不了这么细致)。
定义
对于所有
endpos :一个串的
例:
link :假设现在有两个点,分别是
nxt :
last :整个串的
len :
注意:如果一个点没有他的真超集 ,那么他的
性质
-
对于每个点,endpos 所对应的串的长度一定是连续的。
证明:假设一个串
,选两个两个后缀 和 ,使得 的长度小于 ,且 和 的 都在同一个点中,因为一个串他的串长越短,他所对应的 就越多,所以 的 数小于等于 的 数,且 的 数小于等于 的 数,又因为 和 的 相同,所以 ,所以 也在 所在的点中。 -
中的每一个串后加上 后的 所对应的一定是同一个点,找到 这个点中长度最长的那个串,那么剩余的串一定是他的后缀,假设这个点后面有 ,那么他的后缀后面也全都有 这个点,如果没有,那后缀后面也不会有,所以最后的 一定是一样的。 -
可以发现每次跳
都相当于跳到了一个串的后缀,假设这个后缀的点在原串的位置为 ,那么 这些串都和 这个串在同一个点中。 -
同时可以发现,一个点(假设为
)中的最短子串长度是 。
构造
假设现在要新加入一个点
设
-
也就是说在整个串中没有出现过 串+ 这个子串,那么 可以直接指向 。 -
如果
,设 ,当然这里也需要分成两类讨论。-
若
,那么也就是说 + 这个点后 和 这两个串完全相同,那么 这个点的 就加入了 这个数,可以发现现在的 这个点就是 的 ,因为这个点就是目前 个数最小,并且是 的真超集,如果再跳 ,那么后缀的长度变短,找到的 的个数会不降,所以 。 -
若不等,那么我们可以发现,
这个点中串长度小于等于 的串的 都会新增一个 ,但是长度大于 的并不会新增 ,所以这里我们就被迫把一个点分裂成两个,一个给 ,一个给 中长度大于 的串,我们新建一个节点 来作为分裂成的第一个节点,考虑这里如何转移,假如当前需要找到
的值,那么发现在 这个位置后面是没有 这个字符的,所以说 这个 是无法转移到 这个点的,当我们要找 时,我们的两个点就合并成原先的一个点了,因此 这个点的 要继承 的 。可以发现,原先
的 变成了 的 ,并且 。但是
的每一个后缀的 都多了一个 ,那么如果有原先的 ,那么现在多了一个 ,那么 。最后
和 的 都是 。
-
-
如果
在循环中跳到了 ,说明 这个字符就没在原串中出现过,所以 应该为 ,但是发现 本来就是 ,所以可以直接不管。
最后贴上一个代码(对着理解一下):
struct node{
int nxt[27];
int len,link;
}tr[N];
int cnt[N],lst,tot;
void init(){tr[0].link=-1;}
void insert(int x){
int p=lst,u=++tot;
cnt[u]=1;tr[u].len=tr[lst].len+1;
for (;~p&&!tr[p].nxt[x];p=tr[p].link) tr[p].nxt[x]=u;
if (~p){
int q=tr[p].nxt[x];
if (tr[q].len==tr[p].len+1) tr[u].link=q;
else{
int t=++tot;
copy(tr[q].nxt,tr[q].nxt+26,tr[t].nxt);
tr[t].link=tr[q].link,tr[t].len=tr[p].len+1;
for (;~p&&tr[p].nxt[x]==q;p=tr[p].link) tr[p].nxt[x]=t;
tr[q].link=tr[u].link=t;
}
}
lst=u;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】