模板后缀自动机

Posted ysfac

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了模板后缀自动机相关的知识,希望对你有一定的参考价值。

核心思想:

通过巧妙的设计使得我们能用一个DAG和树的复合结构来在线性复杂度内存储一个串的$n^2$个子串的信息。

 

定义:

1.后缀自动机的结构类似于AC自动机,每个点表示一个endpos等价类(子串结束位置的集合,以下简称为状态),边同AC自动机中的边。即后缀自动机上从根到一个点有若干条路径,这些路径构成的子串在原串中的endpos集合都相同。

2.parent树是一棵建立在后缀自动机节点上的树,建立的方法是类似于线段树的分割(见下文)。它满足任意点u代表的任意一个字符串是u子树中任意一个字符串的后缀。

3.一些需要维护的信息:

struct node{
    int ch[30]; //边(到某个点可能有很多条路径,它们同属于这个点的状态) 
    int dis; //到达该点的所有路径中最长的那一条的长度(该状态最长的串长) 
    int fa; //父亲(串长严格小于儿子;严格是儿子的后缀,但父子之间不一定没有边) 
};

 

性质:

在提到性质之前我们先考虑一下这个endpos等价类的含义。

以$aababa$这个串为例,它有6个可能的endpos,我们现在按长度依次考虑以i为endpos的每个子串$(s[1,i],s[2,i],cdots ,s[i,i])$所属的endpos等价类。

初始都是空串,那么$endpos(空串)={1,2,3,4,5,6}$。

然后考虑长度为1的子串,位置1,2,4,6是‘a‘,位置3,5是‘b‘。那么$endpos(‘a‘)={1,2,4,6}$,$endpos(‘b‘)={3,5}$。

然后考虑长度为2的子串,位置2是‘aa‘,位置3,5是‘ab‘,位置4,6是‘ba‘。那么$endpos(‘aa‘)={2}$,$endpos(‘ab‘)={3,5}$,$endpos(‘ba‘)={4,6}$。

……

自己模拟一下这个过程,不难发现三点性质:

1.随着长度的增加,endpos等价类从集合${1,2,cdots,n}$逐渐划分成若干个小集合。但不是完全划分,中间会因为某个前缀的长度不够而损失掉这个前缀。

2.对于任意两个子串,它们的endpos等价类要么互为子集,要么不相交。显然前者是互为后缀的情况,后者是不互为后缀的情况。

3.同一个endpos等价类包含的子串实际上是某个极长子串的所有后缀。因为我们是在逐渐往每个endpos前面塞字符,那么某个等价类里的所有endpos前面塞完一个字符后,它们代表的字符串可能从相同变得不同。此时该等价类被划分,它的极长子串就是塞之前的子串。

 

构造:

使用增量构造法,就是假设前n-1个字符已经构造好了,考虑插入第n个字符c会怎么样,也就是如何处理插入后新产生的子串(新串的n个后缀)。

先建一个代表${n}$等价类的点np,然后从上一次插入的点lst(即代表${n-1}$等价类,$s[1,n-1]$的点)开始跳fa,每跳到一个点p就把它往np连一条字符边c。直到跳到0(不是根)或者当前点p已经有一条字符c出边。

如果跳到0了说明原串里根本就没出现c这个字符,于是直接记$x[np].fa=rt$即可。

否则,设$q=x[p].to[c]$,分q是或不是新串的后缀两种情况讨论。

当且仅当$x[p].dis+1==x[q].dis$时,q是新串的后缀(q里的最长串就是p里的最长串+c)。此时新串长度$>x[p].dis+1$的后缀都连过边c了,而长度$leq x[p].dis+1$的后缀已经在原串出现过了(就是q代表的所有子串),所以整个系统合法,只需要令$x[np].fa=q$。

否则,q不是新串的后缀(q里的最长串的后缀是p里的最长串+c)。此时我们把q拆成两个点nq和q,前者是新串的后缀(即长度$leq x[p].dis+1$的部分),后者不是。拆完之后把p所有的祖先原来连到q的c边都改到nq(因为p的祖先+c是新串的后缀),然后记$x[nq].fa=x[q].fa,x[q].fa=x[np].fa=nq$即可。此时整个系统合法。

 

代码:

技术图片
#include<bits/stdc++.h>
#define maxn 1000005
#define maxm 500005
#define inf 0x7fffffff
#define ll long long
#define debug(x) cerr<<#x<<": "<<x<<endl
#define fgx cerr<<"--------------"<<endl
#define dgx cerr<<"=============="<<endl

using namespace std;
int hd[maxn<<1],nxt[maxn<<1],to[maxn<<1],ans,cnt;
char str[maxn<<1];

inline int read(){
    int x=0,f=1; char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c==-) f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-0;
    return x*f;
}
inline void addedge(int u,int v){to[++cnt]=v,nxt[cnt]=hd[u],hd[u]=cnt;}

struct SAM{
    struct node{int to[30],fa,siz,dis;}x[maxn<<1];
    int tot=1,lst=1,rt=1;
    inline void extend(int pos){
        int val=str[pos]-a,np=++tot,p=lst; 
        lst=np,x[np].siz=1,x[np].dis=pos;
        for(;p&&!x[p].to[val];p=x[p].fa) x[p].to[val]=np;
        if(!p) x[np].fa=rt;
        else{
            int q=x[p].to[val];
            if(x[q].dis==x[p].dis+1) x[np].fa=q;
            else{
                int nq=++tot; x[nq].dis=x[p].dis+1;
                memcpy(x[nq].to,x[q].to,sizeof(x[q].to));
                x[nq].fa=x[q].fa,x[np].fa=x[q].fa=nq;
                for(;x[p].to[val]==q;p=x[p].fa) x[p].to[val]=nq;
            }
        }
    }
    inline void dfs(int u){
        for(int i=hd[u];i;i=nxt[i]) dfs(to[i]),x[u].siz+=x[to[i]].siz;
        if(x[u].siz!=1) ans=max(ans,x[u].siz*x[u].dis);
    }
}at;

int main(){
    scanf("%s",str+1);
    int n=strlen(str+1);
    for(int i=1;i<=n;i++) at.extend(i);
    for(int i=2;i<=at.tot;i++) addedge(at.x[i].fa,i);
    at.dfs(1),printf("%d
",ans);
    return 0;
}
SAM

以上是关于模板后缀自动机的主要内容,如果未能解决你的问题,请参考以下文章

后缀自动机 模板题

hdu4622(后缀自动机模板)

P3804 模板后缀自动机

vscode 用户代码片段 vue初始化模板 Snippet #新加入开头注释 自动生成文件名 开发日期时间等内容

[P6139] 模板广义后缀自动机 - 广义SAM

Luogu3804模板后缀自动机(后缀自动机)