持续更新qwq(现在只抄写了后缀自动机)

SAM后缀自动机

以下内容摘抄自这里

  • endpos

endpos是一个子串结束为止组成的集合。

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

对于所有结束位置相同的字串,也就是endpos相同的两个子串。他们一个一定是另一个的后缀

两个字符串如果有一个是另一个的后缀,那么较长串的后缀一定是较短串的endpos的子集

两个字符串如果没有后缀的关系,那么他们的endpos的交集一定是空集

后缀自动机的每个节点是依照endpos来划分的,对于endpos相同的子串,我们可以划分在一起。所以我们不难得出一点,对于一堆endpos相同的子串,他们一定互为后缀,并且他们的长度连续。

既然后缀连续,那就一定有一个最长的串,不妨记为longest。那么,所有的其他串一定是它的后缀。随着后缀长度的减小,那么从某一个后缀开始,就可能出现在了更多的位置。那么买这个后缀以及比它更短的后缀的endpos一定会变大,此时他们就会分到别的节点去了。

确定了endpos和长度len就能确定唯一的子串

  • trans

trans是转移的意思,设trans(s,c)表示当前在s状态,接受一个字符c之后所到达的状态。一个状态s表示若干endpos相同的连续子串。

那么此时相当于在后面加上了一个字符c。那么我们对于任意一个串直接加上一个字符c之后,组成的串的endpos还是相同的。所以trans(s,c)就会指向这个状态。

  • parent/suffix links

不妨设一个状态中包含的最短的串叫做shortest。那么我们就知道shortest的任意一个非自己的后缀一定就会出现在了更多的位置。它的那个最长的后缀,也就是减去了第一个字符后的串,就会出现在另外一个状态里,并且是那个状态的longest。

parent tree上,每一个点集的父亲都是自己的后缀。

假设当前状态为s,\(s.shortest.len=parent.longest.len+1\)
所以对于每个状态,没有必要记录shortest,因为你只要知道parent就可以算出来了。

s的endpos是parent的子集
这个不难证明,因为parent包含了更多的位置。

如果trans(s,c)不等于NULL,那么trans(parent,c)不等于NULL

parent是一个完全包含了s的状态,也正因为如此,parent的endpos就是所有儿子endpos的并集

将所有的parent翻过来,我们就得到了parent树。如果要处理什么,就需要parent树的拓扑序。(因为parent相当于包含了它的所有子树,都需要更新上去)。。。不过其实不需要拓扑排序,我们知道s的endpos完全被parent的endpos包含,所以s.longest一定长于parent.longest。所以一个状态的longest越长,它一定要被更先访问。所以,按照longest的长度进行桶排序就可以解决拓扑序了。

  • extend
    对于一个SAM的构造,我们依次加入字符c,来进行构造。

假设原来的字符串是T,首先一定会有一个新节点。因为新加入了一个字符后,一定出现了这个新的字符T+c。此时的endpos一定是新的位置。同时,原来的T的最后一个位置也可以通过+c变到这个新位置。设原来的最后一个位置的状态是last,新的状态是np。所以tans(last,c)=np。

根据前面的东西,我们知道last的祖先们一定也会有这个trans,之后来解决这个问题——

令p=last,一直沿着parent往前跳,也就是不断令\(p=p.parent\),所以p所代表的的,就是越来越短的T的后缀。因为要更新的是最后的位置。只有当存在T的最后一个位置的时候才能更新。

如果\(trans(p,c)=NULL\),直接令\(tans(p,c)=np\)。很显然是可以在后面添加一个c到达np的,如果跳完之后发现没有parent了,直接把np.parent指向1(也就是空串所代表的状态)

如果某个\(trans(p,c)\)不等于NULL,那么设\(q=trans(p,c)\)——
如果有\(longest(p)+1=longest(q)\),那么我们在p的串后面直接添上一个c之后就是q状态。没有任何问题,直接在作为T的后缀的那一个子串上,直接添加一个x显然也可以到达q状态,又因为np所代表的endpos更小,所以\(np.parent=q\)
否则的话,也就是\(longest(q)>longest(p)+1\)(也就是前面那个while更新的时候可能会导致的情况)。如果直接插入的话,相当于给q的endpos强行插入一个np,但是我们发现,如果强行插入进去,这个T+c的后缀会出现在更多的位置,应该属于另外一个状态,不太行。
所以我们新建一个节点nq,相当于把q拆成两部分:一部分是T+c的那个后缀,一个是\(longest(p)+c\),也就是\(longest(nq)=longest(p)+1\),显然T+c的后缀是包含了状态较少的,拆分出来的一部分q是长度较长的。所以\(q.parent=np.parent=nq\)。同时,继续沿着p的parent往上走,把所有的q都替换成nq。

也就是这样——
OI字符串 简单学习笔记 随笔 第1张

比如对于字符串S="aabbabd",它的后缀自动机是:

OI字符串 简单学习笔记 随笔 第2张

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄