声明:这篇是给我自己看的笔记,估计会写得很乱,如果对您没有帮助,建议速换一篇学习。
——————————————————————————————————————————————————————————
后缀自动机(suffix automaton, SAM) 是一个能解决许多字符串相关问题的有力的数据结构。
定义:
\(SAM\)是一个接受\(s\)的所有后缀的最小\(DFA\)(确定性有限自动机或确定性有限状态自动机)。
即:
*\(SAM\)是一张有向无环图,节点叫做状态,边叫做转移。
*有一个\(t_0\)作为原点。
*每个转移都有一个字母,一点出发的所有转移不同。
*有多个终止状态,\(s\)的每个后缀均可用\(t_0 -> end\)的路径表示
*\(SAM\)是满足上述条件的节点数最少的
性质:
\(SAM\)由\(t_0\)出发的任意一条路径都对于\(s\)的一个子串。
how to build:
结束位置:endpos:
记\(endpos(t)\)为\(t\)在\(s\)中所有的结束位置。
两个子串\(t1\)和\(t2\)的endpos集合可能相等,那么按照\(endpos\)来说,可用把所有非空子串分为若干个等价类。
显然\(SAM\)的每个状态对应一个或者多个\(endpos\)相同的子串,\(SAM\)的状态数等于所有等价类的个数 + 1
这里给出一些endpos的性质:
字符串的两个非空子串\(u\)和\(w\),若endpos(u) = endpos(w),则\(u\)在\(s\)中每次出现都以\(w\)的后缀出现
考虑两个\(u\),\(w\)子串,则要么endpos(u) \(in\) endpos(w),要么无交集
考虑一个等价类,则较短者是较长者的后缀。
后缀链接link
考虑\(SAM\)中某个不是\(t_0\)的状态\(v\),则link(v)位该状态对应的等价类的里最长的串的不在该等价类里的最长后缀的等价类。
所有后缀链接构成一棵树
通过endpos集合构造的树,和后缀link的构造的树相同。
算法:
这个算法是在线的算法,逐个加入字符,并维护\(SAM\);
我们只保存\(len\)和\(link\)和每个状态的转移列表,我们不会标记终止状态。
一开始SAM只有一个状态\(t_0\)编号为\(0\),指定\(len = 0,link = -1\)
那么我们如何加入一个字符呢:
- 令\(last\)为添加字符\(c\)前,整个字符串对应的状态。
- 创建一个新的状态cur,并将\(len(cur) = len(last) + 1\),那么考虑怎么求出link(cur)
- 按照以下流程做:从\(last\)开始,如果没有到\(c\)的转移,那么我们添加一个到\(cur\)的转移,遍历后缀link,如果某个状态存在到\(c\)的转移,那么我们记做\(p\),转移到的状态为\(q\).
- 如果没有找到\(p\),则link(cur) = 0
- 现在我们分类讨论一手:
- 如果\(len(p) + 1 = len(q)\)我们将\(link(cur)\)赋值为\(q\)
- 否则就有点复杂:需要复制状态\(q\),创建一个新状态\(clone\),复制除了\(len\)之外的所有信息,将\(len(clone)\)赋值为\(len(p) + 1\),复制了之后我们将后缀链接从\(cur\)往\(clone\)连,从\(q\)往\(clone\)连。最后我们需要使用后缀链接从状态\(p\)往回走,只要存在一条通过\(p\)到\(q\)的转移,就将该转移重定向至\(clone\)
我们还想知道终止状态怎么做,我们可以在为字符串\(s\)构造完完整的\(SAM\)后状态所有的终止状态,遍历整个字符串的状态的后缀link,直到到达初始状态,这样我们将遍历到的所有的状态都标记为终止状态就好了。