零.前置:
(1.init:)初始状态。
(2.end:)结束状态。
(3.E:)结束状态(end)集合。
(4.fa(s):parent)树上(s)的父亲节点。
(5.Reg(s):)节点(s)能达到的(end)的集合。
(6.mx(s):)节点(s)所代表的子串的最长长度。
(7.mn(s):)节点(s)所代表的字串的最短长度。
(8.Right(s):)状态(s)出现的右端点集合。
(9.ST(s):)节点(s)能到达的状态集合(点数)。
一.后缀自动机的性质:
(1.Right(s)subset Right(fa(s)))
(2.mn(s) = mx(fa(s))+1)
(3.s)所代表的所有串在母串中出现的次数和每次出现的右端点相同。
(4.)后缀自动机的(parent)树是原串的反向前缀树(把每个前缀的反串插入到(Trie)树中,并且把没有分支的链合并)。
二.如何求拓扑序:
若(exists e(u,v)) 则 (mx(u) < mx(v));若(fa(v)=u)也能得到(mx(u) < mx(v))。所以将节点按照(mx)数组排序,可以得到拓扑序(基排(O(n)))。
三.如何求Right(s):
若只求大小,可以按照逆拓扑序递推;如果还需要求具体节点,需要平衡树(启发式合并)/可持久化平衡树;如果需要动态维护(|Reg(s)|)(建立的同时维护),则需要一个数据结构,支持在有根树上加边删边,求子树和,因为有根把子树和转化成链上加减,单点查询然后(LCT)即可。
三.如何求ST(s):
逆拓扑序递推,若求本质不同,则每个状态贡献1;若相同算多次,则每个状态贡献(|Right|)次。
其他应用:
(1.)求本质不同子串数量:(sum_{s}mx(s) - mn(s) + 1)。
(2.)求字符串的最小表示:先对(S+S)建立(SAM),然后在(SAM)上跑,每次贪心走字典序最小的出边,走(|S|)步即可。
(3.)求两个字符串的最长公共子串:对一个串建立(SAM),然后跑匹配,如果失配则跳(fa)。
(4.)求后缀树和后缀数组:把反串建(SAM)然后(parent)树就是后缀树,后缀树上(dfs)得到后缀数组。
(5.)字典序(k)小子串:求(ST)然后类似线段树二分的去做。
(6.)求本质不同的子串总长:(sum_{s}sum_{i=mn(s)}^{mx(s)}i)