有关SAM什么的
我好像说过这辈子都是不可能学SAM的真香 主要是我不想初三退役
,后缀自动机,英文名Suffix Automaton,一种搞字符串的东西
**陈丽洁**在
年的冬令营上提出,从此
变得广为人知,下面是
的定义
-
一个串 的后缀自动机 是一个有限状态自动机 ,它能且只能接受所有 的后缀(当然,它能做的事情远远不仅限于后缀).
-
后缀自动机其实是一个 (有向无环图),其中顶点是状态,边代表了状态之间的转移.
-
某一状态 被称为初始状态,由它能够到达其余所有状态.
-
自动机的所有转移,都是一条有向边,且都被某种符号标记,从某一状态出发的所有转移必须拥有不同的标记.
-
一个状态被称为终止状态,表示如果我们从初始状态 经任意一条路径走到某一终止状态,并顺序写出经过边的标记,得到的字符串必定是原串的后缀.
-
在符合上述条件的所有自动机中,后缀自动机拥有最少的状态与转移,并且后缀自动机的状态数以及转移数都是 的.
以下是陈丽洁的《后缀自动机讲稿》
这里下载,aodq
不过非常不建议去看因为根本看不懂
这篇东西是对
的一些感性总结,没有理性证明
所以很短
SAM的优点
其实 ……一言难尽
-
后缀自动机,当然可以识别字符串的所有后缀
扫描二维码关注公众号,回复: 3275803 查看本文章 -
时空复杂度都是
(没有证明) -
代码复杂度极低,码量令人发指地比 自动机还短
-
由于子串一定是某个后缀的前缀,所以 也可以识别子串
-
转移边的 性质使在 上 十分方便
-
边可以套上许多数据结构(树剖、 )来维护,功能强大
-
还有什么其他的我也不知道了
parent边和转移边
- 指向的是字符串 的最长后缀 的右端点
下面是字符串 的 边的栗子
比如字符串 , 为 的最长后缀 的右端点
里除了
边,还有转移边(图中黄色的边是
边,蓝色的是转移边)
每个点表示一个状态,
的转移则通过转移边实现
parent边的连边
对于 ,可以同时把转移边和 边搞出来
-
对于现在刚加入的 点, 为按照字符串顺序走过来的前一个点, 代表新加入的字母
-
从 开始,一直向上找 的 ( )
如果当前的某个 点没有一个儿子连向字母 ,我们就加上这条从 到 的转移边,然后接着找 的 -
直到这个点有儿子连向字母 ,我们就要分两种情况考虑了
举个栗子
这种情况 ,新加入的字母是
其实这张图还少了一条 到 的转移边,字母为
-
设我们向上跳到了第一个符合条件的点是 点, 点指向字母 的儿子是 点
-
在这里 点就是根 ,发现 有指向字母 的儿子节点 号节点(所以 )
-
和 之间的距离为 ,就可以直接把 指向 (也就是 )
但是如果 和 点的距离大于 呢?比如
这种情况 ,新加入的字母是
此时 点是根 , ,但是我们不能把 赋值为 ,因为 不是 的后缀
-
连错误 边的原因是我们把 当成了 (因为从根 到 有两条路)
-
也就是误认为 是 的后缀
此时 需要加点操作
加点操作
-
我们新加一个点,从 到新点、从新点到 都连上字母 的转移边
-
注意这个新点不存在原串里,只是 的一个分身虚点,所以 点的所有转移边信息全部复制给新点
-
新点的 即为 原来的 , 和 的 边都重新指向新点
-
除此之外我们还要继续向 边回溯直到根 ,把所有指向 的转移边重新指向新点
对于上面 的情况,应该这样连边
最终 的 边性质没有被破坏,加点操作正确
code&其他
- 这样的话 就建完了
void sam(int x)
{
int p;
len[++tot]=len[last]+1,sum[tot]=1,now=tot;
for (p=last;p && !son[p][x];p=parent[p])son[p][x]=now;
if (p)
{
int q=son[p][x];
if (len[q]>len[p]+1)
{
len[++tot]=len[p]+1;
memcpy(son[tot],son[q],sizeof(son[q]));
parent[tot]=parent[q];
parent[q]=parent[now]=tot;
for (;son[p][x]==q;p=parent[p])son[p][x]=tot;
}
else parent[now]=q;
}
else parent[now]=1;
last=now;
}
- 再再比如说 的 是这样的
SAM的一些应用
可是很有用的
询问一个串 是否是一个串 的子串
-
建 的
-
然后 在 上面跑
询问一个串 有多少个不同的子串
-
建 的
-
从根 出发的任意一条转移边路径都是一个不同的子串
-
答案即为根 出发不同路径数量
#其实SAM真的很简单……
本人版权意识薄弱……