后缀自动机的构造

后缀自动机的构造算法采用的是逐个在字符串后面添加字符的在线做法
p 为当前字符串最长的那个后缀(就是当前整个字符串)所代表的节点,现在我们要添加一个字符c,可以这样思考:

  1. 加入 c 之后的整个串必然构成一个新节点,因为他右端点集合只有curlen一个,而 curlen 在之前的节点的右端点集合中必然没有出现过。不妨记为 np 。考虑所有 p 的祖先,也就是加入c之前所有当前后缀,他们现在后面多了一个字符 c ,我们的目标就是要让这些新产生的子串仍然能被后缀自动机识别。记任意一个p在par树上的祖先为 p0 ,分两种情况讨论:
    1. ch[p0][c]==null ,那么对于任意一个属于 p0 的串 s ,s+c必然属于np,这种情况只需要设置 ch[p0][c]=np 即可.
    2. ch[p0][c]!=null 这表示之前就有一个子串是当前串的后缀,我们不需要改变什么就已经能识别 s+c
  2. 接着,我们需要找到这个新节点在 par 树上的父亲。也就是我们需要找到一个最长的后缀,他在当前串中至少出现两次。由于当前的后缀必然是上一次的后缀之后添加一个字符 c 得到,因此,我们可以遍历p的祖先,找到第一个具有 c 边的节点,记这个节点为p,令 ch[p][c]=q 这里又有两种情况.
    1. q 这个节点所代表的所有子串都可以由p代表的某个子串 +c 得到,即 ml[q]==ml[p]+1 那么这个 q 就是我们要求的,只需设置f[np]=q即可.
    2. ml[q]>ml[p]+1 ,这表示节点 q 代表的某些子串并不是当前的后缀,解决方法也很简单,只需要单独的把那些是当前后缀的从q中分裂出来,记分裂出来的节点为 nq ,那么需要改变:
      • nq 设置为 q par
      • q 的所有出边都复制一份给nq
      • 将所有指向 q 的边都改成指向nq,由于所有指向 q 的边必然来源与ppar
      • f[np]=nq
  3. 从构造算法中可以看出,每添加一个字符,最多新增两个节点,因此节点总数不会多于 n+n
#include<bits/stdc++.h>
using namespace std;
const int Maxn=123456;
struct suffix_automaton
{
    int ml[Maxn],f[Maxn],ch[Maxn][26];
    int sz,last;
    void init()
    {
        memset(ch[0],0,sizeof(ch[0]));
        last=sz=1;
    }
    int getidx(char c){return c-'a';}
    void add(char x)
    {
        int c=getidx(x);
        int p=last,np=sz++;ml[np]=ml[p]+1;last=np;
        memset(ch[np],0,sizeof(ch[np]));
        while(p&&!ch[p][c])ch[p][c]=np,p=f[p];
        if(!p){f[np]=0;return;}
        int q=ch[p][c];
        if(ml[q]==ml[p]+1){f[np]=q;return;}
        int nq=sz++;
        memcpy(ch[nq],ch[q],sizeof(ch[q]));
        ml[nq]=ml[p]+1;
        f[nq]=f[q];
        f[q]=nq;
        while(p&&ch[p][c]==q)ch[p][c]=nq,p=f[p];
        f[np]=nq;
    }
}solver;
int main()
{
    solver.init();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值