后缀自动机的应用
零.前置:
\(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\)