后缀自动机

如果我们想要建立一个自动机,使它能够接收一个串s的所有后缀,那么最暴力的想法应该就是,直接用这个串的所有后缀来建立一个AC自动机

后缀自动机

上面就是用aabbabd这个字母串作为母串建立的AC自动机(没有画fail边,好吧,那它就是个字母树...),嗯,建出这个东西的确就可以接收此串的所有后缀了,但是但是,聪明的朋友就会发现,这玩意儿的节点个数是O(n^2)级别的….我只给了你一个串,你就用了串长度平方个节点,害得我对于两位数的节点只能画更大的圆,太坑爹了吧。

所以我们就需要YY出一个O(n)级别的节点个数和转移数的自动机也就是后缀自动机来处理这个问题。

假设我们已经YY出了一个可以接收串S的后缀自动机A,那么我们考虑给这个串的后面添加一个字符x,串S变成了Sx,我们来想一想,对于一个后缀Ax,在A中会有哪些节点应该拥有这样的转移,从而到达接收状态。

举个例子比较好,还是用aabbabd这个串(fhq举的那个例子是个回文串太囧了)

首先只有根节点

后缀自动机

就像这样(大家先忽略掉那个红色的数字)

现在我要在原串(空串)后面加上一个字母a,那么原先可以接收的状态在改造自动机后应该仍然能够到达接收状态

后缀自动机

就像这样,我记下每个点接收状态中串的长度的最大值l[i],就像上面的红色数字,再插入一个a

后缀自动机

可以看到此时的s节点并没有向当前新加入的二号节点连接转移的边,这是为什么呢?因为S节点已经有了一条符号为a的转移,想想我们原先连接转移的意义:这个点原先是作为可以接收的节点而存在的(可以接收某个后缀),所以我们现在依然要使得它原先能够接收的那个后缀在加上当前的这个字符后能够被接收,因此由它向当前新建节点连接转移,而如果它原本就已经有了名为当前字符的转移,假设它是a点,它当前字符的转移会到达b点,那么现在有两种情况:

  1. l[b] = l[a]:也就是说,能够到达b点的最长的串会是从a点转移过去的,那么b号点完全可以作为一个接受状态,因为到达它的一定会是当前串的后缀(比这个点能接收的最长的串短的也会是后缀_),因此只需要把b点设为接收状态就行了。
  2. l[b] > l[a]:此时我们就不能单纯地把b设为接收状态,因为这样的话会有一些原本不应该被接收的状态能够转过来,但是我们又需要能够接收状态,那么我们新建一个点c,使l[c] = l[a]+1,那么将c设为接收状态,原先b点能够转到的状态全部给c复制一份即可,当然,原先会转到b的可以接收后缀的那些点的转移就应该要全部移到c点了

这样每次最多添加一个点,点数是O(n)的,至于边数的证明嘛,自己想想吧,想到的可以留言哦!

我把aabbabd的后缀自动机接下来的过程贴出来(蓝色的边是为了方便找到上一个可以接收后缀的点,记为f[i])

后缀自动机

后缀自动机

后缀自动机

呃,这就是最后的后缀自动机的样子啦,只有9个节点,是不是很漂亮呢?

我们再来回顾一下整个建立后缀自动机的过程(伪代码有一处错误,请大家找一找,可以看看你对这个构造的理解程度,但是c++代码是没错的):

C=当前插入的字符串

K=新建一个节点

P=上一次的尾节点(一定是可以接收原先的后缀的节点)

Q=第一个有C转移边的祖先

If (不存在Q)

f[K] = S

else

H=Q沿C转移边到达的节点

If (l[H] == l[Q] + 1)

f[K] = H

else

O=新建一个节点

O=H, l[O] = l[Q] + 1

f[H] = f[Q] = O

Q和所有Q的C转移边指向H祖先的C转移边改为指向O

P和所有Q以下的P的祖先的C转移边指向K

很短很精巧,用c++来阐释就大概是这么个

后缀自动机

原文链接: https://www.cnblogs.com/ericchou1990/archive/2012/12/11/2813064.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    后缀自动机

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/72221

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月9日 下午3:15
下一篇 2023年2月9日 下午3:18

相关推荐