AC自动机

来源:互联网 发布:ipad老是无法加入网络 编辑:程序博客网 时间:2024/05/29 07:36

字符串这一方面的算法。
概述:在Trie树上使用KMP算法得到Fail数组,使用Fail数组建树,
那么每次查询的串就是当前位置到Fail树的根得到查询,类似于一个后缀。
利用这个性质以及树上的各种算法可以做某些方面的题。
清空这一方面,一次性全部清空或动态清空均可,考虑到可能会写内存池,采用后者。
在Fail树上可以对当前点的信息或是回答进行记录,部分题目可以均摊复杂度。
https://vjudge.net/contest/202341#overview

T1 WP

类似于模板,分类后使用一个函数来完成某个方向上的答案收集。
可能是看代码长度的题目。

Code

#include<iostream>#include<cstring>#include<cstdio>using namespace std;#define Komachi is retarded#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)#define M 1004#define N 500004int n,m,q;char Map[M][M],C[M];int Rx[]={-1,-1,0,1,1,1,0,-1},Ry[]={0,1,1,1,0,-1,-1,-1};struct Node{    int x,y;    char c;    void Pri(int d){        printf("%d %d %c\n",x-Rx[c-'A']*d,y-Ry[c-'A']*d,c);    }};int Pos[M];struct Trie{    int Next[N][26],Fail[N],Dep[N],sz;    Node Ans[N];    bool Mark[N];    void Add(int x){        scanf("%s",C);        int p=0;        REP(i,0,strlen(C)){            int &nt=Next[p][C[i]-'A'];            if(!nt)nt=++sz,Dep[nt]=Dep[p]+1;            p=nt;        }        Pos[x]=p;    }    int Q[N];    void ReBuild(){        int l,r;l=r=0;        REP(i,0,26)if(Next[0][i])Q[r++]=Next[0][i];        while(l<r){            int A=Q[l++];            REP(i,0,26){                int &p=Next[A][i];                if(p)Fail[Q[r++]=p]=Next[Fail[A]][i];                else p=Next[Fail[A]][i];            }        }    }    void Answer(int p,Node ans){        while(p){            if(Mark[p])return;            Ans[p]=ans;            Mark[p]=1;            p=Fail[p];        }    }    void Answer(int x,int y,int Rx,int Ry,char c){        int p=0;        while(x>=0 && y>=0 && x<n && y<m){            int nt=Next[p][Map[x][y]-'A'];            p=nt?nt:Fail[p];            Answer(p,(Node){x,y,c});            x+=Rx,y+=Ry;        }    }}Trie;int main(){    scanf("%d%d%d",&n,&m,&q);    REP(i,0,n)scanf("%s",Map[i]);    Trie.Dep[0]=-1;    REP(i,0,q)Trie.Add(i);    Trie.ReBuild();    REP(i,0,n){        REP(g,0,8)Trie.Answer(i,0,Rx[g],Ry[g],g+'A');        REP(g,0,8)Trie.Answer(i,m-1,Rx[g],Ry[g],g+'A');    }    REP(i,0,m){        REP(g,0,8)Trie.Answer(0,i,Rx[g],Ry[g],g+'A');        REP(g,0,8)Trie.Answer(n-1,i,Rx[g],Ry[g],g+'A');    }    REP(i,0,q){        int p=Pos[i];        Trie.Ans[Pos[i]].Pri(Trie.Dep[Pos[i]]);    }    return 0;}

T2 RA

比较有意思的一道题。
考虑到n的范围小,如果已知两两之间串的拼接代价,可以利用状态压缩DP来求解。
所以用AC自动机将所有串加入,得出无法被访问的节点并标记。
从某个可行串的结尾点出发BFS求解到其他串结尾点的最小距离。
然后回到上一个问题去计算。

Code

#include<iostream>#include<cstring>#include<cstdio>#include<queue>using namespace std;#define Komachi is retarded#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)#define M 60004#define K 1024#define chkmin(a,b) a=min(a,b)int n,m,Pos[14],Len[14];char C[M];struct AC_Auto{    int Next[M][2],Fail[M],sz;    bool Ban[M];    void Clear(){memset(Next[0],sz=0,sizeof(Next[0]));}    int NewNode(){sz++;memset(Next[sz],Ban[sz]=Fail[sz]=0,sizeof(Next[sz]));return sz;}    void Add(int x,bool k){        scanf("%s",C);        int p=0;        REP(i,0,strlen(C)){            int &nt=Next[p][C[i]-'0'];            if(!nt)nt=NewNode();            p=nt;        }        if(k)Pos[x]=p,Len[x]=strlen(C);        else Ban[p]=1;    }    int Q[M];    void ReBuild(){        int l,r;l=r=0;        REP(i,0,2)if(Next[0][i])Q[r++]=Next[0][i];        while(l<r){            int A=Q[l++];            REP(i,0,2){                int &p=Next[A][i];                if(p)Fail[Q[r++]=p]=Next[Fail[A]][i];                else p=Next[Fail[A]][i];            }            Ban[A]|=Ban[Fail[A]];        }    }    int Lenth[14][14],Dis[M];    void BFS(int s){        int l,r;l=r=0;Q[r++]=s;        memset(Dis,63,sizeof(Dis));        Dis[s]=0;        while(l<r){            int A=Q[l++],B;            REP(i,0,2)if(Dis[B=Next[A][i]]==0x3f3f3f3f && !Ban[B])                Q[r++]=B,Dis[B]=Dis[A]+1;        }    }    int DP[14][1044],To[14][14];    void Answer(){        memset(To,63,sizeof(To));        REP(i,0,n){            BFS(Pos[i]);            REP(j,0,n)To[i][j]=Dis[Pos[j]];        }        memset(DP,63,sizeof(DP));        REP(i,0,n)DP[i][1<<i]=Len[i];        REP(i,1,1<<n)REP(j,0,n)if(DP[j][i]!=0x3f3f3f3f){            REP(k,0,n)chkmin(DP[k][i|(1<<k)],DP[j][i]+To[j][k]);        }        int Ans=0x3f3f3f3f;        REP(i,0,n)Ans=min(Ans,DP[i][(1<<n)-1]);        printf("%d\n",Ans);    }}AC;int main(){    while(scanf("%d%d",&n,&m)!=EOF){        if(!n && !m)break;        AC.Clear();        REP(i,0,n)AC.Add(i,1);        REP(i,0,m)AC.Add(i,0);        AC.ReBuild();        AC.Answer();    }    return 0;}

T3 HABT

后缀和前缀的最大匹配长度,
转换到AC自动机上是A串结尾点在Fail树上到根的节点中包含的B串的最长长度。
离线询问,DFS,采用直接修改,记录旧值的方法得到当前栈内的点的答案。
复杂度为O(n),常数可能较大。

#include<iostream>#include<cstring>#include<cstdio>using namespace std;#define Komachi is retarded#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)#define LREP(i,A,E) for(int i=E.Head[A];i;i=E.Next[i])#define M 100044inline void chkmax(int &a,const int &b){if(a<b)a=b;}int n,m,len,L[M],R[M],Nm[444],Len[M],Fr[M],Pos[M];char C[M];template<const int Len,const int Num>struct Linklist{    int Next[Len],Head[Num],Val[Len],tot;    int operator [](int x){return Val[x];}    void pb(int u,int v){Next[++tot]=Head[u],Val[Head[u]=tot]=v;}    void Clear(){memset(Head,tot=0,sizeof(Head));}};Linklist<M,M>E,AD,Qes,Rev;int Tmp[M],B[M],Ans[M],Mx[M];struct AC_Auto{    int Next[M][4],Fail[M],sz;    void Clear(){        REP(i,0,sz+1)memset(Next[i],Fail[i]=0,sizeof(Next[i]));        sz=0;    }    void Add(int x,int L,int R){        int p=0,lt=0;        REP(i,L,R){            int &nt=Next[p][Nm[C[i]]];            if(!nt)nt=++sz;            Len[i]=++lt;            AD.pb(p=nt,i);            Fr[i]=x;        }        Pos[x]=p;    }    int Q[M];    void ReBuild(){        int l,r;l=r=0;        REP(i,0,4)if(Next[0][i])Q[r++]=Next[0][i];        while(l<r){            int A=Q[l++];            REP(i,0,4){                int &p=Next[A][i];                if(p)Fail[Q[r++]=p]=Next[Fail[A]][i];                else p=Next[Fail[A]][i];            }            E.pb(Fail[A],A);        }    }}AC;void Answer(int A){    LREP(i,A,AD){        int j=AD[i];        Tmp[j]=Mx[Fr[j]];        chkmax(Mx[Fr[j]],Len[j]);        Rev.pb(A,j);    }    LREP(i,A,Qes){        int j=Qes[i];        Ans[j]=Mx[B[j]];    }    LREP(i,A,E)        Answer(E[i]);    LREP(i,A,Rev){        int j=Rev[i];        Mx[Fr[j]]=Tmp[j];    }}int main(){    Nm['A']=0;    Nm['G']=1;    Nm['C']=2;    Nm['T']=3;    while(scanf("%d%d",&n,&m)!=EOF){        memset(Mx,0,sizeof(Mx));        AC.Clear();        AD.Clear();        E.Clear();        Qes.Clear();        Rev.Clear();        len=0;        REP(i,0,n){            L[i]=len;            scanf("%s",C+len);            len+=strlen(C+len);            R[i]=len;            AC.Add(i,L[i],R[i]);        }        REP(i,0,m){            int a;            scanf("%d%d",&a,&B[i]);            a--,B[i]--;            Qes.pb(Pos[a],i);        }        AC.ReBuild();        Answer(0);        REP(i,0,m)printf("%d\n",Ans[i]);    }    return 0;}

T4 GREW

在Fail树上DP,线段树优化查询。

Code

#include<iostream>#include<cstring>#include<cstdio>using namespace std;#define Komachi is retarded#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)#define LREP(i,A,E) for(int i=E.Head[A];i;i=E.Next[i])#define M 300004inline void chkmax(int &a,const int &b){if(a<b)a=b;}int T,n,m,len,L[M],R[M],W[M],Pos[M],Ans,DFN[M],Ed[M],dfn;char C[M];template<const int Len,const int Num>struct Linklist{    int Next[Len],Head[Num],Val[Len],tot;    int operator [](int x){return Val[x];}    void pb(int u,int v){Next[++tot]=Head[u],Val[Head[u]=tot]=v;}    void Clear(){memset(Head,tot=0,sizeof(Head));}};Linklist<M,M>E;struct Segtree{    #define lp (p<<1)    #define rp (p<<1|1)    #define lson l,mid,lp    #define rson mid+1,r,rp    int Mx[M<<2];    void Clear(){memset(Mx,0,sizeof(Mx));}    void Updata(int l,int r,int p,int a,int b,int t){        if(l>b || r<a)return;        if(a<=l&&r<=b){chkmax(Mx[p],t);return;}        int mid=l+r>>1;        Updata(lson,a,b,t);        Updata(rson,a,b,t);    }    int Query(int l,int r,int p,int a,int b){        if(l>b || r<a)return 0;        if(a<=l&&r<=b)return Mx[p];        int mid=l+r>>1;        return max(Mx[p],max(Query(lson,a,b),Query(rson,a,b)));    }}Tree;struct AC_Auto{    int Next[M][26],Fail[M],sz;    void Clear(){        REP(i,0,sz+1)memset(Next[i],Fail[i]=0,sizeof(Next[i]));        sz=0;    }    void Add(int x,int L,int R){        int p=0;        REP(i,L,R){            int &nt=Next[p][C[i]-'a'];            if(!nt)nt=++sz;            p=nt;        }        Pos[x]=p;    }    int Q[M];    void ReBuild(){        int l,r;l=r=0;        REP(i,0,26)if(Next[0][i])Q[r++]=Next[0][i];        while(l<r){            int A=Q[l++];            REP(i,0,26){                int &p=Next[A][i];                if(p)Fail[Q[r++]=p]=Next[Fail[A]][i];                else p=Next[Fail[A]][i];            }            E.pb(Fail[A],A);        }    }}AC;void DFS(int A){    DFN[A]=++dfn;    LREP(i,A,E)DFS(E[i]);    Ed[A]=dfn;}int main(){    scanf("%d",&T);    REP(Case,1,T+1){        Ans=dfn=len=0;        AC.Clear();        E.Clear();        Tree.Clear();        scanf("%d",&n);        REP(i,0,n){            L[i]=len;            scanf("%s",C+len);            len+=strlen(C+len);            R[i]=len;            AC.Add(i,L[i],R[i]);            scanf("%d",&W[i]);        }        AC.ReBuild();        DFS(0);        REP(i,0,n){            int A=Pos[i];            int DP=0,p=0;            REP(j,L[i],R[i]){                p=AC.Next[p][C[j]-'a'];                chkmax(DP,Tree.Query(1,AC.sz+1,1,DFN[p],DFN[p]));            }            Tree.Updata(1,AC.sz+1,1,DFN[A],Ed[A],DP+W[i]);            chkmax(Ans,DP+W[i]);        }        printf("Case #%d: %d\n",Case,Ans);    }    return 0;}

T5 S

按照上面的方法来的话写起来比较诡异。
查询前缀+后缀,即在后缀的结尾点上在Fail树上的子树那些点有该前缀的点数。
Hash去重,在Fail树上用Dsu维护Trie树可以写出来。

#include<iostream>#include<cstring>#include<cstdio>#include<map>using namespace std;#define Komachi is retarded#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)#define LREP(i,A,E) for(int i=E.Head[A];i;i=E.Next[i])#define M 500044#define ULL unsigned long long#define Base 233map<ULL,int>Mark;ULL Bc[M],H1[M],H2[M];ULL Hash(ULL *H,int l,int r){    return H[r]-H[l-1]*Bc[r-l+1];}int T,n,m,len,Beg[M],Pr[M],Ans[M];char C[M],Prx[M],Sux[M];template<const int Len,const int Num>struct Linklist{    int Next[Len],Head[Num],Val[Len],tot;    int operator [](int x){return Val[x];}    void pb(int u,int v){Next[++tot]=Head[u],Val[Head[u]=tot]=v;}    void Clear(){memset(Head,tot=0,sizeof(Head));}};Linklist<M,M>E,D,Qes;struct AC_Auto{    int Next[M<<1][26],Fail[M<<1],sz;    int Cnt[M<<1];    void Clear(){memset(Next[0],sz=0,sizeof(Next[0]));}    int NewNode(){        sz++;        memset(Next[sz],Fail[sz]=Cnt[sz]=0,sizeof(Next[sz]));        return sz;    }    int Add(int x){        int p=0;        REP(i,Beg[x],Beg[x+1]){            int &nt=Next[p][C[i]-'a'];            if(!nt)nt=NewNode();            Cnt[p=nt]++;        }        return p;    }    int Q[M<<1];    void ReBuild(){        int l,r;l=r=0;        REP(i,0,26)if(Next[0][i])Q[r++]=Next[0][i];        while(l<r){            int A=Q[l++];            REP(i,0,26){                int &p=Next[A][i];                if(p)Fail[Q[r++]=p]=Next[Fail[A]][i];                else p=Next[Fail[A]][i];            }            E.pb(Fail[A],A);        }    }    void Query(int x){        int p=0;        REP(i,0,strlen(Sux)){            int &nt=Next[p][Sux[i]-'a'];            if(!nt)nt=NewNode();            p=nt;        }        Qes.pb(p,x);    }    int QueryC(int x){        int p=0;        REP(i,Pr[x],Pr[x+1]){            int nt=Next[p][Prx[i]-'a'];            if(!nt)return 0;            p=nt;        }        return Cnt[p];    }}AC,Trie;int Sz[M],Son[M];void Get(int A){    int B;    Sz[A]=0;    LREP(i,A,D)Sz[A]++;    Son[A]=M-1;    LREP(i,A,E){        Get(B=E[i]);        Sz[A]+=Sz[B];        if(Sz[B]>Sz[Son[A]])Son[A]=B;    }}void Add(int A){    LREP(i,A,D)Trie.Add(D[i]);    LREP(i,A,E)Add(E[i]);}void Answer(int A){    int B;    LREP(i,A,E)if((B=E[i])!=Son[A])        Answer(B),Trie.Clear();    if(Son[A]!=M-1)Answer(Son[A]);    LREP(i,A,E)if((B=E[i])!=Son[A])Add(B);    LREP(i,A,D)Trie.Add(D[i]);    LREP(i,A,Qes){        int j=Qes[i];        Ans[j]+=Trie.QueryC(j);    }}int main(){    Bc[0]=1;    REP(i,1,M)Bc[i]=Bc[i-1]*Base;    scanf("%d",&T);    while(T--){        scanf("%d%d",&n,&m);        len=0;        AC.Clear();        E.Clear();        D.Clear();        Qes.Clear();        Mark.clear();        memset(Ans,0,sizeof(Ans));        REP(i,0,n){            scanf("%s",C+len);            len+=strlen(C+len);            Beg[i+1]=len;            D.pb(AC.Add(i),i);        }        REP(i,0,n){            ULL Val=0;            REP(j,Beg[i],Beg[i+1])                Val=Val*Base+C[j];            Mark[Val]++;        }        len=0;        REP(i,0,m){            int a,b;            scanf("%s%s",Prx+len,Sux);            a=strlen(Prx+len);            b=strlen(Sux);            Pr[i+1]=len+a;            AC.Query(i);            REP(j,0,a)H1[j+1]=H1[j]*Base+Prx[len+j];            REP(j,0,b)H2[j+1]=H2[j]*Base+Sux[j];            REP(j,1,min(a,b)+1)if(Hash(H1,a-j+1,a)==Hash(H2,1,j))                Ans[i]-=Mark[Hash(H1,1,a-j)*Bc[b]+H2[b]];            len+=a;        }        AC.ReBuild();        Trie.Clear();        Get(0);        Answer(0);        REP(i,0,m) printf("%d\n",Ans[i]);    }    return 0;}

另外有一种比较巧妙的方法。
将查询在前缀和后缀中插入一个特殊字符并拼接来作为模式串。
将所有原来的串用 S+特殊字符+S 拼接来作为主串。
所有主串均进行一次匹配,结束后再Hash去重即可。

#include<iostream>#include<cstring>#include<cstdio>#include<map>using namespace std;#define Komachi is retarded#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)#define LREP(i,A,E) for(int i=E.Head[A];i;i=E.Next[i])#define M 604444#define ULL unsigned long long#define Base 233map<ULL,int>Mark;ULL Bc[M],H1[M],H2[M];ULL Hash(ULL *H,int l,int r){    return H[r]-H[l-1]*Bc[r-l+1];}int T,n,q,len,Beg[M],Ans[M],Pos[M];char C[M<<1],Prx[M<<1],Sux[M];template<const int Len,const int Num>struct Linklist{    int Next[Len],Head[Num],Val[Len],tot;    int operator [](int x){return Val[x];}    void pb(int u,int v){Next[++tot]=Head[u],Val[Head[u]=tot]=v;}    void Clear(){memset(Head,tot=0,sizeof(Head));}};Linklist<M,M>E;struct AC_Auto{    int Next[M][27],Fail[M],sz;    int Cnt[M];    void Clear(){memset(Next[0],sz=0,sizeof(Next[0]));}    int NewNode(){        sz++;        memset(Next[sz],Cnt[sz]=Fail[sz]=0,sizeof(Next[sz]));        return sz;    }    void Add(int x,int l){        int p=0;        REP(i,0,l){            int &nt=Next[p][Sux[i]-'a'];            if(!nt)nt=NewNode();            p=nt;        }        Pos[x]=p;    }    int Q[M];    void ReBuild(){        int l,r;l=r=0;        REP(i,0,27)if(Next[0][i])Q[r++]=Next[0][i];        while(l<r){            int A=Q[l++];            REP(i,0,27){                int &p=Next[A][i];                if(p)Fail[Q[r++]=p]=Next[Fail[A]][i];                else p=Next[Fail[A]][i];            }            E.pb(Fail[A],A);        }    }    void Answer(int x){        int p=0;        REP(i,Beg[x],Beg[x+1])Cnt[p=Next[p][C[i]-'a']]++;    }    void DFS(int A){        LREP(i,A,E)DFS(E[i]),Cnt[A]+=Cnt[E[i]];    }}AC;int main(){    Bc[0]=1;    REP(i,1,M)Bc[i]=Bc[i-1]*Base;    scanf("%d",&T);    while(T--){        scanf("%d%d",&n,&q);        Mark.clear();        memset(Ans,len=0,sizeof(Ans));        AC.Clear();        E.Clear();        REP(i,0,n){            scanf("%s",C+len);            int m=strlen(C+len);            len+=m;            C[len++]=26+'a';            ULL Val=0;            REP(j,0,m)Val=Val*Base+(C[len++]=C[Beg[i]+j]);            Beg[i+1]=len;            Mark[Val]++;        }        REP(i,0,q){            scanf("%s%s",Prx,Sux);            int a=strlen(Prx),b=strlen(Sux);            REP(j,0,a)H1[j+1]=H1[j]*Base+Prx[j];            REP(j,0,b)H2[j+1]=H2[j]*Base+Sux[j];            REP(j,1,min(a,b)+1)if(Hash(H1,a-j+1,a)==Hash(H2,1,j))                Ans[i]-=Mark[Hash(H1,1,a-j)*Bc[b]+H2[b]];            Sux[b]=26+'a';            REP(j,0,a)Sux[b+j+1]=Prx[j];            AC.Add(i,a+b+1);        }        AC.ReBuild();        REP(i,0,n)AC.Answer(i);        AC.DFS(0);        REP(i,0,q)printf("%d\n",Ans[i]+AC.Cnt[Pos[i]]);    }    return 0;}
原创粉丝点击