短小精悍的字符串杀手——后缀自动机(SAM)学习小记(update guadually...)

自动机

  何为自动机?(我发现我以前竟然不知道)一个有限状态自动机(DFA)的功能就是识别字符串,若自动机A能识别字符串S则记为A(S)=true,否则A(s)=false。
  自动机由五个部分组成,alpha:字符集,state:状态集合,init:初始状态,end:结束状态集合,trans:状态转移函数。
  对于转移函数trans(s,c),表示的是在状态s所代表的字符串后,添加一个字符c所到达的状态。这就像AC自动机和回文自动机中的next指针。

定义

  后缀自动机,顾名思义,就是用来识别一个字符串的所有后缀的自动机。不过,由于它是在线加点的(一个一个地加),所以之前的状态也会被保留,于是它还能识别那个字符串的所有子串。这正是后缀自动机的强大之处。
  设后缀自动机的母串为S。对于S的子串str,设right[str]表示str每次在S中出现时右端点位置组成的集合。
  考虑一个字符串str,若它是S的子串,则SAM(str)就应该为一个合法状态;反之,SAM(str)就是一个非法状态,或者说空状态。也就是说,对于S的每一个子串,后缀自动机中都应有一个相应的状态。
  但是,如果我们对于其每个子串都建立一个状态,那将会有 O ( | S | 2 ) 个状态。这显然不尽人意。
  那么我们可以使用最简状态自后缀自动机。顾名思义,它就是状态数最少的后缀自动机,因为它省略了许多无用的状态。在后面可以证明它的大小是线性的。
  考虑S的一个子串str。若right[str]=right[a+str](a为一个字符串),则我们可以将它们合并为同一个状态。因为我们从它们开始添加字符,能得到的字符串都是一样的。
  我们考虑S的两个子串A、B。若right[A]与right[B]有交,则显然其中一个是另一个的后缀。设B为A的后缀,则right[A] right[B]。也就是说,对于S的两个子串A、B,它们的right要么没有交集,要么其中一个就是另一个的后缀。我们令一个状态s的父状态fa[s]为满足right[s] right[fa[s]](注意,是 )且|right[fa[s]]|最小的状态。换句话说,s的父状态fa[s]为s的最长后缀。

性质

  一个状态s表示一个或多个字符串,且它们的right集合相等。
  若设min[s]为s代表的字符串中的最短长度,len[s]为其中的最长长度,则s所代表的字符串长度为一个区间,为[min[s],len[s]]。
  len[fa[s]]=min[s]-1。

构造方法

  后缀自动机有一种非常简单的构造方法,据说叫增量法。
  若我们当前已建立了前|S|-1个字符的后缀自动机,且现在表示区间[1,|S|-1]的状态为last。现在加入第|S|个字符,设其为c。
  首先我们必须新建一个状态cur,因为在此之前没有任何状态可以代表[1,|S|]这个字符串。len[cur]自然设为|S|。
  现在需要转移。显然我们应该加入一个last→cur的转移,然后要加入一个fa[last]→cur的转移……直到这个状态已有一个→cur的转移。设这个状态为p,trans(p,c)为q。
  此时,q会多代表一个字符串:右端点为|S|且长度为len[p]+1的串。那么就必须要分类讨论:
  
  1.len[q]=len[p]+1。此时q多代表的这个字符串定然是cur的子串,因为我们之前从last往上跳都是沿着fa边,而这样只会跳到它的后缀,所以此时q代表的最长字符串肯定和我们当前[1,|S|]的后缀一致,只是right[q]会多一个|S|而已。所以我们简单地让fa[cur]=q。
  
  2.len[q]>len[p]+1。此时q所代表的字符串中,长度≤len[p]+1的串的right会多出一个|S|,而长度>len[p]+1的则不会。因为我们按照不断地跳后缀,len[q]应该=len[p]+1,但是在p后面多加一个c竟然增加了超过1的长度,这说明q所表示的字符串中不单单只有p后面加个c;而这可能与我们当前[1,|S|]的后缀不一致。
  为了维护q所代表的所有字符串的right集合均相等,我们可以克隆一个状态clone,clone代表的是q原本代表的、所有长度≤len[q]+1的串,因此len[clone]=len[p]+1,且clone的其他信息(fa和trans)与q一致。而此时的q代表的字符串缩减为了长度为[len[p]+2,len[q]]的字符串。因此fa[q]=fa[cur]=clone。
  然后我们从p开始,沿着fa链不断往上跳,对于经过的状态s,若trans(s,c)为q,则改为clone,直到跳到一个trans(s,c)不为q为止。

  这样,就完成了对后缀自动机的构造。

图解

  声明:这些图片来自这篇博客
这里写图片描述
这里写图片描述
这里写图片描述

例题

【洛谷3804 】后缀自动机

Problem

  给定一个只包含小写字母的字符串S( | S | 10 6 ),请你求出 S 的所有出现次数不为 1 的子串的出现次数乘上该子串长度的最大值。

Solution

  这题真的是后缀自动机的模板了。
  但是,要注意一点,这题需要我们求出那些本质不同的子串,并且还要累计出现次数。
  我们知道,同一状态的right集合一致,所以它代表的字符串的出现次数也一致,所以可设siz[i]表示状态i的出现次数。初始时所有非clone节点的siz=1。
  但是由于fa[i]表示的是状态i的最长后缀,所以siz[fa[i]]应该+=siz[i]。那么就需要确定一个顺序。
  显然len[fa[i]]<len[i],于是可按len[i]排序,然后再累加siz。具体排序方法可参考下面的Code。

Code
#include <cstdio>
#include <cstring>
#include <iostream>
#define ll long long
#define max(a,b)(((a)>(b))?(a):(b))
using namespace std;
const int maxN=2e6+1,maxK=26;
char s[maxN];
int n,i,fa[maxN],siz[maxN],len[maxN],c[maxN],a[maxN];
struct SAM{
    ll ans,now,u,v,np,root,cnt,last,i,sam[maxN],ch[maxN][maxK];
    ll newnode(ll x){
        len[++cnt]=x,fa[cnt]=0;
        for (i=0;i<maxK;i++)
            ch[cnt][i]=0;
        return cnt;
    }
    void init(){
        cnt=0;
        newnode(0);
        last=root=1;
    }
    void extend(ll c,ll i){
        now=newnode(len[last]+1),u=last;
        for (;u && !ch[u][c];u=fa[u]) ch[u][c]=now;
        if (!u) fa[now]=1;else{
            v=ch[u][c];
            if (len[u]+1==len[v]) fa[now]=v;else {
                np=newnode(len[u]+1),memcpy(ch[np],ch[v],sizeof(ch[np]));
                fa[np]=fa[v],fa[v]=fa[now]=np;
                for (;u && ch[u][c]==v;u=fa[u]) ch[u][c]=np;
            }
        }
        siz[now]=1;
        last=now;
    }
    void work(){
        ans=0;
        for (i=1;i<=cnt;i++)c[len[i]]++;//计算每种len出现的次数
        for (i=1;i<=cnt;i++) c[i]+=c[i-1];//从短到长求前缀和
        for (i=1;i<=cnt;i++) a[c[len[i]]--]=i;//这样可保证len[a[i]]>=len[a[i-1]]
        for (i=cnt;i;i--) siz[fa[a[i]]]+=siz[a[i]];//累加siz
        for (i=1;i<=cnt;i++){
            if (siz[i]>1)
                ans=max(ans,siz[i]*len[i]);
        }
        printf("%lld",ans);
    }
} run;
int main(){
    scanf("%s",s+1),n=strlen(s+1);
    run.init();
    for (i=1;i<=n;i++)
        run.extend(s[i]-'a',i);
    run.work();
}

猜你喜欢

转载自blog.csdn.net/qq_36551189/article/details/79764070