【知识点】后缀自动机

后缀自动机:

简介:

通过巧妙的设计使得我们能用一个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,然后从上一次插入的点last(即代表$\{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$即可。此时整个系统合法。

关于为什么$x[p].dis+1\neq x[q].dis$时q不是新串的后缀:因为如果是,那么q中最长串去掉c后必然是一个长度$>x[p].dis$的原串后缀,且它代表的节点有c出边。那么这个节点应该先于p被遍历到并且停下,而这并没有发生,所以q一定不是新串的后缀。

 

代码:

#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\n",ans);
    return 0;
}
后缀自动机

 

广义后缀自动机:

没啥区别,每次插入一个串的时候直接把last设为1就行了,相当于从头开始插入。

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

using namespace std;
char str[maxn];

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;
}

struct RAMauto{
    int to[maxn<<1][30],dis[maxn<<1],fa[maxn<<1],vis[maxn<<1],tot;
    inline void insert(int n){
        int las=1;
        for(int i=1;i<=n;i++){
            int ch=str[i]-'a',p=las,now=++tot; 
            dis[now]=i,las=now;
            while(p && !to[p][ch]) to[p][ch]=now,p=fa[p];
            if(!p) fa[now]=1;
            else{
                int q=to[p][ch];
                if(dis[q]==dis[p]+1) fa[now]=q;
                else{
                    int nq=++tot; dis[nq]=dis[p]+1;
                    memcpy(to[nq],to[q],sizeof(to[q]));
                    fa[nq]=fa[q],fa[q]=fa[now]=nq;
                    while(p && to[p][ch]==q) to[p][ch]=nq,p=fa[p];
                }
            }
        }
    }
    inline ll dfs(int u){
        if(vis[u]) return 0; vis[u]=1; 
        ll res=(ll)dis[u]-(ll)dis[fa[u]];
        for(int i=0;i<26;i++)
            if(to[u][i]) res+=dfs(to[u][i]);
        return res;
    }
}RAM;

int main(){
    int T=read(); RAM.tot=1;
    while(T--) scanf("%s",str+1),RAM.insert(strlen(str+1));
    printf("%lld\n",RAM.dfs(1));
    return 0;
}
广义后缀自动机

 

posted @ 2020-02-10 22:01  Fugtemypt  阅读(178)  评论(0编辑  收藏  举报