后缀自动机学习小记

来源:互联网 发布:美微网络电视柠檬tv 编辑:程序博客网 时间:2024/06/06 01:38

前言:

我学习这个东西吧是很懵的,结合了多篇论文和博客才搞懂最基础的构建,需要细细琢磨。

推荐论文:

陈立杰冬令营上的论文,有些小错误,而且我讨厌指针。
张天扬集训队的论文,里面讲了许多应用。

推荐博客:

后缀自动机学习总结——functioner.

后缀自动机的定义:

我也没搞清楚。
有限状态自动机的就是能识别字符串。
而后缀自动机就能识别一个字符串的所有后缀,当然同时能识别子串。

后缀自动机的构建:

一个显然的想法是把所有后缀扔到一个叫AC自动机的东西里去。
时间复杂度:…
空间复杂度:…
你懂的~~

我们要构建的自动机自然不是上面这种劣质产品,而是最简状态的后缀自动机。

有多简呢?时空复杂度:O(n)

*接下来的构造讲的非常简略,可以观赏。

son[N][26]表示子节点。
pre[N]表示上一个能接受后缀的点。
step[N]表示从root到这个点的最长距离。
last表示最后一个能接受后缀的点。

假设现在的字符串是T,要在T的后面加一个x.

可以新建一个np代表x。

last沿着pre链条,如果son里没有x所代表的字符,就指向np。

直到点p,p的x所代表字符的子节点已经有了,设这个点为q,此时不能直接指过去,因为会覆盖掉q。

思考p能接受后缀意味着什么:
这表明,从root到p的所有路径都是T的后缀。

1.step[q] = step[p] +1

由于到p的路径都是T的后缀,而step[q] = step[p] +1,保证了要想到q,要么经过p,要么直接从root来。
后缀自动机有一个很重要的性质,就是对于一个点w,能直接转移到它的点一定在一条pre链上。
那么如果有w能够转移到q,w所代表的也是后缀。
因此可以直接将q作为新的可以接受后缀的点。

2.step[q]>step[p]+1

此时如果直接像前面一样,不一定可行,因为可能夹杂其他字符。

怎么办呢?

可以建一个nq到x后面,使step[nq]=step[p]+1,那么就和前面的作用一样了。

即把q的son和pre都copy给nq,np、q的pre只能是nq,

最后把p的pre链上的点的子节点是q的变成nq。

这样p的pre链上的就往nq跑了,其他的就往q跑了。

复杂度分析:

空间复杂度因为每次最多加两个点,显然O(n)。

按pre边建树。

从root出发,沿着树边走到u。

u走一条非树边到v,接着走,一定能走到一个后缀。

可以说成每一条点会对应一条非树边。

所以总边数是O(n)。

注意这里是没有考虑不同字符数的常数的。

Code:

#include<cstdio>#include<string>#include<cstring>#include<iostream>#include<algorithm>#define fo(i, x, y) for(int i = x; i <= y; i ++)#define mem(a) memset(a, 0, sizeof a)#define max(a, b) ((a) > (b) ? (a) : (b))#define min(a, b) ((a) < (b) ? (a) : (b))using namespace std;const int N = 5e5 + 5;struct suffix_automation {    char s[N];    int son[N][26], pre[N], step[N], last, tot;    void push(int v) {step[++ tot] = v;}    void Extend(int c) {        push(step[last] + 1);        int p = last, np = tot;        for(;p && !son[p][c]; p = pre[p]) son[p][c] = np;        if(!p) pre[np] = 1; else {            int q = son[p][c];            if(step[q] > step[p] + 1) {                push(step[p] + 1);                int nq = tot;                memcpy(son[nq],son[q],sizeof son[q]);                pre[nq] = pre[q]; pre[q] = pre[np] = nq;                for(; son[p][c] == q; p = pre[p]) son[p][c] = nq;            } else pre[np] = q;        }        last = np;    }    void Build() {        scanf("%s", s);        tot = last = 1;        mem(son); mem(pre); mem(step);        for(int i = 0, E = strlen(s); i < E; i ++) Extend(s[i] - 'a');    }} suf;int main() {    suf.Build();}
原创粉丝点击