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,采用直接修改,记录旧值的方法得到当前栈内的点的答案。
复杂度为
#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;}
- AC自动机...
- AC自动机
- AC 自动机
- AC自动机
- AC自动机
- ac自动机
- ac自动机
- AC自动机
- AC自动机
- AC自动机
- AC自动机
- AC自动机
- AC 自动机
- ac自动机
- AC自动机
- AC自动机
- AC自动机
- AC自动机
- Java基础——从键盘(控制台)输入字符串(数据)的几种方式详解
- eclipse tomcat add and remove工程异常
- 主题模型(LDA)(二)-公式推导
- python dota2数据 3 下载胜负数据
- poj1163
- AC自动机
- upstream timed out (110: Conn ection timed out)
- C++ Primer Plus 编程练习 3.7.4
- 欢迎使用CSDN-markdown编辑器
- 最全的Vue学习资料
- maven
- 多项式求和
- Win7操作系统下安装protocol buffer2.4.1
- Java过滤HTML标签,获取纯文本