后缀自动机重启之后缀自动机归来

1.endpos

对于字符串 s s s s s s的子串 t t t,定义 e n d p o s ( t ) endpos(t) endpos(t)为一个集合,这个集合的元素为 t t t每次在 s s s中出现的结束位置。
例: s = a b c a a b c s=abcaabc s=abcaabc, e n d p o s ( a ) = { 1 , 4 , 5 } , e n d p o s ( b c ) = { 3 , 7 } endpos(a)=\{1,4,5\},endpos(bc)=\{3,7\} endpos(a)={1,4,5},endpos(bc)={3,7}
特别的,定义 e n d p o s ( 空 串 ) = { 0 , 1 , 2 , . . . , n − 1 } endpos(空串)=\{0,1,2,...,n-1\} endpos()={0,1,2,...,n1}

2.等价类

如果 s s s的两个字符串 t 1 , t 2 t1,t2 t1,t2满足 e n d p o s ( t 1 ) = e n d p o s ( t 2 ) endpos(t1)=endpos(t2) endpos(t1)=endpos(t2),则称 t 1 , t 2 t1,t2 t1,t2属于同一个等价类。
s s s的字串 t 1 , t 2 , . . . , t m − 1 , t m ( ∀ i ≠ j , t i ≠ t j ) t_1,t_2,...,t_{m-1},t_m(\forall i\neq j,t_i\neq t_j) t1,t2,...,tm1,tm(i=j,ti=tj)属于同一个等价类,且没有其它 s s s的子串也属于这个等价类,则将字符串集 t 1 , t 2 , . . . , t m − 1 , t m t_1,t_2,...,t_{m-1},t_m t1,t2,...,tm1,tm称为 s s s的一个等价类。后缀自动机中的每个结点都是一个等价类。
后缀自动机的结点数 = = =等价类的数量(包括空串),将后缀自动机中结点 i i i表示的等价类称为等价类 i i i
特别的,结点 1 1 1表示空串,并称为结点 1 1 1为起始节点。

3.引理

定义 s u f ( x , y ) suf(x,y) suf(x,y)表示串 x x x是串 y y y的后缀。
引理:对于 s s s的字串 t 1 , t 2 , t 3 t_1,t_2,t_3 t1,t2,t3,满足 ∣ t 1 ∣ < ∣ t 2 ∣ < ∣ t 3 ∣ |t_1|<|t_2|<|t_3| t1<t2<t3 s u f ( t 1 , t 2 ) , s u f ( t 2 , t 3 ) , e n d p o s ( t 1 ) = e n d p o s ( t 3 ) suf(t_1,t_2),suf(t_2,t_3),endpos(t_1)=endpos(t_3) suf(t1,t2),suf(t2,t3,endpos(t1)=endpos(t3),则 e n d p o s ( t 2 ) = e n d p o s ( t 1 ) = e n d p o s ( t 3 ) endpos(t_2)=endpos(t_1)=endpos(t_3) endpos(t2)=endpos(t1)=endpos(t3)
证:显然 e n d p o s ( t 3 ) ⊂ e n d p o s ( t 2 ) ⊂ e n d p o s ( t 1 ) endpos(t_3)\subset endpos(t_2)\subset endpos(t_1) endpos(t3)endpos(t2)endpos(t1),故若 e n d p o s ( t 1 ) = e n d p o s ( t 3 ) endpos(t_1)=endpos(t_3) endpos(t1)=endpos(t3)必有结论成立。
推论:若 t 1 , t 2 , . . . , t m − 1 , t m t_1,t_2,...,t_{m-1},t_m t1,t2,...,tm1,tm s s s的一个等价类,且 ∣ t 1 ∣ < ∣ t 2 ∣ < . . . < ∣ t m − 1 ∣ < ∣ t m ∣ |t_1|<|t_2|<...<|t_{m-1}|<|t_m| t1<t2<...<tm1<tm),则必有 ∣ t i ∣ − ∣ t i − 1 ∣ = 1 ( 1 < i ≤ m ) , s u f ( t i − 1 , t i ) |t_i|-|t_{i-1}|=1(1<i\le m),suf(t_{i-1},t_i) titi1=1(1<im),suf(ti1,ti)

4.link

设等价类 i i i的字串集为 t 1 , t 2 , . . . , t m t_1,t_2,...,t_m t1,t2,...,tm,设字串 t t t包含于等价类 j j j中,且满足 ∣ t ∣ = ∣ t 1 ∣ − 1 , s u f ( t , t 1 ) |t|=|t_1|-1,suf(t,t_1) t=t11,suf(t,t1),则定义 l i n k ( i ) = j link(i)=j link(i)=j
性质:当 l i n k ( i ) = j link(i)=j link(i)=j,有 e n d p o s ( t 1 ) ⊊ e n d p o s ( t ) endpos(t_1)\subsetneq endpos(t) endpos(t1)endpos(t),所有的 l i n k link link构成一颗以起始节点 1 1 1为根的树。

5.nex

引理:设 t 1 , t 2 , . . . , t m t_1,t_2,...,t_m t1,t2,...,tm为一个等价类,则对于任意的字符 c c c,字串集 t 1 + c , t 2 + c , . . . , t m + c t_1+c,t_2+c,...,t_m+c t1+c,t2+c,...,tm+c仍然属于同一个等价类(这个等价类可以是一个已经存在的等价类,也可以是一个新的等价类)。

若等价类 i i i中的所有字符后面都接上一个字符 c c c后,新的字串集属于等价类 j j j,则称 n e x ( i , c ) = j nex(i,c)=j nex(i,c)=j(若 j j j不存在,则 n e x ( i , c ) = n u l l nex(i,c)=null nex(i,c)=null)。
n e x nex nex称为后缀自动机的转移函数(从某个等价类转移到另一个等价类)。

6.len

定义 l e n ( i ) len(i) len(i)表示等价类 i i i的字串集中长度最大的字符串的长度。其最小长度为 l e n ( l i n k ( i ) ) + 1 len(link(i))+1 len(link(i))+1
性质: l e n ( i ) − l e n ( l i n k ( i ) ) len(i)-len(link(i)) len(i)len(link(i))为等价类 i i i中的字符串个数。

7.后缀自动机的构建

令字符串 s = s 1 s 2 s 3 . . . s n s=s_1s_2s_3...s_n s=s1s2s3...sn,令 p r e ( x ) = s 1 s 2 . . . s x ( 1 ≤ x ≤ n ) pre(x)=s_1s_2...s_x(1\le x\le n) pre(x)=s1s2...sx(1xn),令 p r e ( 0 ) pre(0) pre(0)表示空串,现要构建 s s s的后缀自动机。
采用归纳法构造:
1. p r e ( 0 ) pre(0) pre(0)为空串,其后缀自动机只有一个起始结点(等价类),其 n e x = n u l l , l e n = 0 , l i n k = 0 nex=null,len=0,link=0 nex=null,len=0,link=0
2.假设当前已经构建了 p r e ( i − 1 ) pre(i-1) pre(i1)的后缀自动机,现构建 p r e ( i ) pre(i) pre(i)的后缀自动机,这等价于在子串 s i , s i − 1 s i , s i − 2 s i − 1 s i , . . . s_i,s_{i-1}s_i,s_{i-2}s_{i-1}s_i,... si,si1si,si2si1si,... e n d p o s endpos endpos中添加一个 i i i,并且必然会添加一个新的结点(因为 e n d p o s ( p r e ( i ) ) = { i } endpos(pre(i))=\{i\} endpos(pre(i))={i})。

引理:设构建 p r e ( i ) pre(i) pre(i)的后缀自动机时时新建的结点为 x x x,则结点 x , l i n k ( x ) , l i n k ( l i n k ( x ) ) , . . . , 1 x,link(x),link(link(x)),...,1 x,link(x),link(link(x)),...,1的字串集的并集为 p r e ( i ) pre(i) pre(i)的后缀集。

l a s t last last表示构建 p r e ( i − 1 ) pre(i-1) pre(i1)时新建的结点,令 n o w now now表示构建 p r e ( i ) pre(i) pre(i)时新建的结点,由 l e n ( n o w ) = l e n ( l a s t ) + 1 len(now)=len(last)+1 len(now)=len(last)+1,再分以下情况考虑:
n e x ( l a s t ) ( s i ) = n u l l nex(last)(s_i)=null nex(last)(si)=null:在 p r e ( i − 1 ) pre(i-1) pre(i1)所属的等价类后面新加字符 s i s_i si,新的子串集属于等价类 n o w now now,令 n e x ( l a s t ) ( s i ) = n o w nex(last)(s_i)=now nex(last)(si)=now即可。由引理,继续递归检查 l a s t last last 1 1 1 l i n k link link路径上的结点。

l a s t last last l i n k link link路径上存在结点 p p p满足 n e x ( p ) ( s i ) = q ≠ n u l l nex(p)(s_i)=q\neq null nex(p)(si)=q=null,且 p p p的任意前驱结点 x x x都满足 n e x ( x ) ( s i ) = n u l l nex(x)(s_i)=null nex(x)(si)=null:等价类 p p p的字串集加上字符 s i s_i si后属于等价类 q q q,但是等价类 q q q中为 p r e ( i ) pre(i) pre(i)后缀的字串集新加入值 i i i e n d p o s endpos endpos,又分两种情况讨论:
l e n ( q ) = l e n ( p ) + 1 len(q)=len(p)+1 len(q)=len(p)+1,则 q q q的字符集都为 p r e ( i ) pre(i) pre(i)的后缀,又由 l i n k link link的定义,令 l i n k ( n o w ) = q link(now)=q link(now)=q即可。
l e n ( q ) > l e n ( p ) + 1 len(q)>len(p)+1 len(q)>len(p)+1,则 q q q中长度大于 l e n ( p ) + 1 len(p)+1 len(p)+1的不是 p r e ( i ) pre(i) pre(i)的后缀,因此把长度 ≤ l e n ( p ) + 1 \le len(p)+1 len(p)+1这部分分离出来,构成一个新的结点 n q nq nq,不难证明 l i n k ( q ) = l i n k ( n o w ) = n q , n e x ( n q ) = n e x ( s ) link(q)=link(now)=nq,nex(nq)=nex(s) link(q)=link(now)=nq,nex(nq)=nex(s) , l i n k ( n q ) = p ,link(nq)=p ,link(nq)=p。而对于 p p p 1 1 1 l i n k link link路径上的其它结点 o p op op,若 n e x ( o p ) ( s i ) = q nex(op)(s_i)=q nex(op)(si)=q,则令 n e x ( o p ) ( s i ) = n q nex(op)(s_i)=nq nex(op)(si)=nq,否则其一定属于 l i n k ( s ) link(s) link(s)路径上,也属于新构建后 l i n k ( n o w ) link(now) link(now)的路径上(显然 l i n k ( s ) link(s) link(s)之后的字串集都为 p r e ( i ) pre(i) pre(i)的后缀)。

#include<bits/stdc++.h>
using namespace std;
const int N=2e6+5;
int last=1,tot=1,fa[N],sum[N],endpos[N],len[N],t[N][26];
char s[N];
void add(int x)
{
    int p=last,n=++tot;
    last=n;endpos[n]=1;len[n]=len[p]+1;
    while(p&&!t[p][x]) t[p][x]=n,p=fa[p];
    if(!p) fa[n]=1;
    else
    {
        int q=t[p][x];
        if(len[p]+1==len[q]) fa[n]=q;
        else
        {
            int nq=++tot;
            for(int i=0;i<26;i++)
                t[nq][i]=t[q][i];
            fa[nq]=fa[q];fa[q]=nq;
            fa[n]=nq;len[nq]=len[p]+1;
            while(p&&t[p][x]==q) t[p][x]=nq,p=fa[p];
        }
    }
}
queue<int>q;
int main()
{
    scanf("%s",s+1);
    for(int i=1;s[i];i++)
        add(s[i]-'a');
    for(int i=2;i<=tot;i++)
        sum[fa[i]]++;
    for(int i=2;i<=tot;i++)
        if(!sum[i]) q.push(i);
    while(!q.empty())
    {
        int u=q.front();q.pop();
        endpos[fa[u]]+=endpos[u];
        sum[fa[u]]--;
        if(!sum[fa[u]]) q.push(fa[u]);
    }
    int ans=0;
    for(int i=2;i<=tot;i++)
        if(endpos[i]!=1)
        ans=max(ans,endpos[i]*len[i]);
    printf("%d\n",ans);
}

8.广义后缀自动机

当构造多串的后缀自动机时,只要注意,上述是用 p r e ( i − 1 ) pre(i-1) pre(i1)的自动机构造 p r e ( i ) pre(i) pre(i)的自动机,令 p r e ( i − 1 ) + c 1 = p r e ( i ) pre(i-1)+c_1=pre(i) pre(i1)+c1=pre(i),如果再构建一个 p r e ( i − 1 ) + c 2 = p pre(i-1)+c_2=p pre(i1)+c2=p的自动机,那么只需要把 p r e ( i − 1 ) + c 1 pre(i-1)+c_1 pre(i1)+c1 p r e ( i − 1 ) + c 2 pre(i-1)+c_2 pre(i1)+c2看成不同等价类即可(虽然它们的 e n d p o s endpos endpos相同,但是其字符串不相等)。
简单来说,只要对等价类定义做点小小的修改: t 1 , t 2 t_1,t_2 t1,t2属于同一等价类,必须满足 s u f ( t 1 , t 2 ) ∣ ∣ s u f ( t 2 , t 1 ) suf(t_1,t_2)||suf(t_2,t_1) suf(t1,t2)suf(t2,t1),且 e n d p o s ( t 1 ) = e n d p o s ( t 2 ) endpos(t_1)=endpos(t_2) endpos(t1)=endpos(t2)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e5+5;
string s[N];
int n,k;
ll ans[N];
int las=1,tot=1,sa[N],f[N],vis[N],num[N],sum[N],len[N],son[N][26],fa[N];
int work(int p,int c)
{
    int nq=++tot,q=son[p][c];
    len[nq]=len[p]+1;
    fa[nq]=fa[q];fa[q]=nq;
    memcpy(son[nq],son[q],sizeof(son[q]));
    while(p&&son[p][c]==q) son[p][c]=nq,p=fa[p];
    return nq;
}
int ins(int p,int c)
{
    if(son[p][c])
    {
        int q=son[p][c];
        if(len[q]==len[p]+1) return q;
        else return work(p,c);
    }
    else
    {
        int np=++tot;len[np]=len[p]+1;
        while(p&&!son[p][c]) son[p][c]=np,p=fa[p];
        if(!p) fa[np]=1;
        else
        {
            int q=son[p][c];
            if(len[q]==len[p]+1) fa[np]=q;
            else fa[np]=work(p,c);
        }
        return np;
    }
}
void solve()
{
    int u;
    for(int i=1;i<=n;i++)
    {
        u=1;
        for(int j=0;j<s[i].size();j++)
        {
            u=son[u][s[i][j]-'a'];int p=u;
            while(p&&vis[p]!=i) sum[p]++,vis[p]=i,p=fa[p];
        }
    }
    for(int i=1;i<=tot;i++) num[len[i]]++;
    for(int i=1;i<=tot;i++) num[i]+=num[i-1];
    for(int i=tot;i>=1;i--) sa[num[len[i]]--]=i;
    for(int i=2;i<=tot;i++)
        u=sa[i],f[u]=f[fa[u]]+(sum[u]>=k?len[u]-len[fa[u]]:0);
    for(int i=1;i<=n;i++)
    {
        u=1;
        for(int j=0;j<s[i].size();j++)
            u=son[u][s[i][j]-'a'],ans[i]+=f[u];
    }
}
int main()
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    cin>>n>>k;
    for(int i=1;i<=n;i++)
    {
        cin>>s[i];
        las=1;
        for(int j=0;j<s[i].size();j++)
            las=ins(las,s[i][j]-'a');
    }
    solve();
    for(int i=1;i<=n;i++)
        printf(i==n?"%lld\n":"%lld ",ans[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值