首先补一点自动机的知识,
有状态集合,字符集,初始状态,终止状态和状态转移,反正我是学了AC自动机才理解这些东西的。
当时Clj讲的时候,感觉很快,各种概念没有跟上来,现在回头弄一弄。
首先如果直接用后缀构字典树的话发现最坏会达到n^2级别的点。
如下aabbabd的构树。
那么现在就要想办法减少节点。
现在的任务就是构一种图,使得从s到终止状态的路径都是后缀,且这些后缀包含了所有的后缀。
后缀自动机就是在n级别的点数完成了这个工作。
考虑一个字符一个字符的添进去。
需要记的东西除了和字典树一样的东西之外还要记一个f[],表示最近的一个可以接受后缀的位置,也就是离这个点最近的
添完上一个字符之后的终止状态。还需要记一个l[]表示到这个点的最长路径的长度。
首先完成归纳法的第一步,初始状态肯定是很好完成的。红色数字就是l,蓝色边是f,其他的就是字典树的东东。
接下来考虑加入字符,假设添加到第i-1个字符是符合要求的,考虑第i个字符。
首先说做法。
用last记最后加入的点是谁.以它为起点往前找,设添的字母是ch[i],当然要新建一个从last出发以ch[i]为边的p。
如果那个点a没有ch[i]的出边,那么就直接指向p,否则分两种情况,假设指向b点。
1.
只需要把b直接赋成合法状态即可,因为到b的路径代表的字符串依次成后缀关系,然后因为只有a转到b这条路径是最长的,
自然所有到b的路径都变成了后缀了(因为a与i-1成后缀关系,都在后面添ch[i]仍然成后缀关系)。
2.
这种情况下,如果直接像1那么弄就悲剧了,因为a可以经过某些点再到达b,那么这些路径就不是后缀了,但是必须要接受后缀。
怎么办?那么就要新建点了r,把b指出去的信息复制一份,把指向b的与a有后缀关系的指向r即可。
可能文字有点烦,还是看图吧,有图思路就清晰了。
依旧是aabbabd这个串。
上面已经有了两步了
好吧为了解释复杂度的问题,先亮代码。
呵呵!其实我开始一直没有想清的为什么不要把一堆的父亲全部扫完。
其实原因很简单,把所有需要建新边的点扫完之后,碰上一个已经有这种边了的,
那么之前的肯定都已经有这种边了,因为父亲的指向是后缀严格递减的。
那么当前扫到的这个点用这种边走到的点的父亲的信息我们只需要接下来接着用就行了,反正都成后缀关系了。
好吧,现在就可以证明复杂度了,首先,每次要则增加一个点用来接收last指向它的边,
然后往前面扫f,扫到一个xxx,就终止了,然后新增一个点,所以点数是O(n)级别的。
然后看边数,如果是字符串,那么总边数最多就只有26×n了,好吧,勉勉强强算O(n)级别了,