后缀自动机学习小记

前言:

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

推荐论文:

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

推荐博客:

后缀自动机学习总结——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();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值