【学习笔记】ac自动机&fail树
-
定义
- 解决文本串和多个模式串匹配的问题;
- 本质是由多个模式串形成的一个字典树,由tie的意义知道:trie上的每一个节点都是一个模式串的前缀;
- 在trie上加入fail边,一个节点fail边指向这个节点所代表的前缀的最长后缀节点(除开自身的后缀);
- 也就是说如果x->y,那么y所代表的串是x所代表的串在trie上出现过的最大后缀;
-
例子
- (黑边为trie,红边为fail)
- 以"hers","she","his","i"为例:
- 原谅我的画图水平。。。如果有精通graphViz的求指点
-
构造算法
- 离线算法,先对所有模式串建出trie,同时可以用cnt表示一个节点的有模式串的个数;
- 规定根节点为0,fl[0] = 0,0的直接儿子的fl[v]=0;
- 一个节点的后缀可以由它trie树上的父亲转移而来,所以按照深度bfs;
- 假设u->v之间的边为c;
- 所以只需要沿着fl[u],fl[fl[u]],...,一直往上跳,找到第一个有c边的u',那么v'就是v的fl;
- 实际实现中可以将$ch[u][i]==0$补成$fl[u]$,避免暴力往上跳;
-
1 int n,len,ch[N][26],sz,cnt[N],fl[N]; 2 char s[N]; 3 queue<int>q; 4 void ins(){ 5 int u=0; 6 for(int i=0,c;i<len;i++){ 7 c=s[i]-'a'; 8 if(!ch[u][c])ch[u][c]=++sz; 9 u=ch[u][c]; 10 } 11 cnt[u]++; 12 } 13 void get_fl(){ 14 fl[0]=0; 15 for(int i=0;i<26;i++)if(ch[0][i]){ 16 fl[ch[0][i]]=0; 17 q.push(ch[0][i]); 18 } 19 while(!q.empty()){ 20 int u=q.front();q.pop(); 21 for(int i=0;i<26;i++){ 22 int&v = ch[u][i]; 23 if(!v){v=ch[fl[u]][i];continue;} 24 fl[v]=ch[fl[u]][i]; 25 q.push(v); 26 } 27 } 28 }
-
性质
-
1.匹配
- 文本串和模式串的匹配只需不断$u=ch[u][s[i]-'a']$即可;
- 如果匹配到自动机的一个点,那么意味着沿着fail边走到的点都被匹配了;
-
2.fail树
- 由于一个点的fail指针唯一,且所有点的fail都和0连通,所以fail边形成了一个数的结构;
- 沿着u的fail祖先往上走会找到u节点的所有后缀节点;
- 对于字符串s,在自动机里匹配到的所有节点的所有fail祖先就表示s的所有子串;
-
-
习题
- 1.bzoj3172[Tjoi2013]单词
- ac自动机模板,由于trie树上面只存储了前缀,子串的出现次数要按照深度从大到小对每个点向fail指针累加cnt;
1 #include<cstdio> 2 #include<iostream> 3 #include<algorithm> 4 #include<cstring> 5 #include<queue> 6 #include<cmath> 7 #include<vector> 8 #include<stack> 9 #include<map> 10 #include<set> 11 #define Run(i,l,r) for(int i=l;i<=r;i++) 12 #define Don(i,l,r) for(int i=l;i>=r;i--) 13 #define ll long long 14 #define ld long double 15 #define inf 0x3f3f3f3f 16 #define mk make_pair 17 #define fir first 18 #define sec second 19 #define il inline 20 #define rg register 21 #define pb push_back 22 using namespace std; 23 const int N=1000010; 24 int n,ch[N][26],sz,fl[N],sum[N],ans[N],id[N]; 25 char s[N]; 26 int q[N],t,w; 27 void ins(int now){ 28 int l=strlen(s),u=0; 29 for(int i=0;i<l;i++){ 30 int&v=ch[u][s[i]-'a']; 31 if(!v)v=++sz; 32 sum[u=v]++; 33 } 34 id[now]=u; 35 } 36 void solve(){ 37 for(int i=0;i<26;i++){if(ch[0][i])q[++w]=ch[0][i];} 38 while(t<w){ 39 int u=q[++t]; 40 for(int i=0;i<26;i++){ 41 int&v=ch[u][i]; 42 if(!v)v=ch[fl[u]][i]; 43 else fl[v]=ch[fl[u]][i],q[++w]=v; 44 } 45 } 46 for(int i=w;i;i--)sum[fl[q[i]]]+=sum[q[i]]; 47 for(int i=1;i<=n;i++)printf("%d\n",sum[id[i]]); 48 } 49 int main(){ 50 // freopen("bzoj3172.in","r",stdin); 51 // freopen("bzoj3172.out","w",stdout); 52 scanf("%d",&n); 53 for(int i=1;i<=n;i++){scanf("%s",s);ins(i);} 54 solve(); 55 return 0; 56 }//by tkys_Austin; 57
- 2.bzoj1212[Hnoi2004]L语言
- n,m都很小;$f[i]$表示前缀i是否可以被理解,$f[i]$可以从$f[j](j<i)$转移的条件是$s[j+1]---S[i]$可以被理解,暴力判断应该会T
- 对字典建自动机,如果把文章跑一遍,可以直接用fail指针暴力往上跳就可以找到所有的可以转移的j;
1 #include<bits/stdc++.h> 2 #define rg register 3 #define il inline 4 using namespace std; 5 const int N=1100010; 6 int n,m,len,ch[N][26],sz,lst[N],head,tail,q[N],dep[N],vis[N],fl[N],ans; 7 bool f[N]; 8 char s[N]; 9 il char gc(){ 10 static char*p1,*p2,s[1000000]; 11 if(p1==p2)p2=(p1=s)+fread(s,1,1000000,stdin); 12 return(p1==p2)?EOF:*p1++; 13 } 14 il int rd(){ 15 int x=0;char c=gc(); 16 while(c<'0'||c>'9')c=gc(); 17 while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+c-'0',c=gc(); 18 return x; 19 } 20 il void gt(){ 21 char *p = s,c = gc(); 22 while(c<'a'||c>'z')c=gc(); 23 while(c>='a'&&c<='z')*p++ = c,c=gc(); 24 len = p - s; 25 } 26 il void ins(){ 27 int u=0; 28 for(rg int i=0;i<len;i++){ 29 if(!ch[u][s[i]-'a'])ch[u][s[i]-'a']=++sz; 30 u = ch[u][s[i]-'a']; 31 } 32 vis[u] = 1; 33 } 34 void get_fl(){ 35 for(int i=0;i<26;i++)if(ch[0][i]){ 36 q[++tail]=ch[0][i]; 37 dep[ch[0][i]]=1; 38 } 39 while(head<tail){ 40 int u=q[++head]; 41 for(int i=0;i<26;i++){ 42 int&v=ch[u][i]; 43 if(!v){v=ch[fl[u]][i];continue;} 44 dep[v] = dep[u] + 1; 45 fl[v] = ch[fl[u]][i]; 46 if(vis[fl[v]])lst[v]=fl[v];else lst[v]=lst[fl[v]]; 47 q[++tail]=v; 48 } 49 } 50 } 51 il bool find(int x,int pos){ 52 if(!x)return false; 53 if(vis[x]&&f[pos-dep[x]])return true; 54 return find(lst[x],pos); 55 } 56 void query(){ 57 f[0] = true; 58 for(rg int i=1,u=0;i<=len;i++){ 59 u = ch[u][s[i-1]-'a']; 60 f[i] = find(u,i); 61 if(f[i]) ans = i; 62 } 63 } 64 int main(){ 65 freopen("bzoj1212.in","r",stdin); 66 freopen("bzoj1212.out","w",stdout); 67 n=rd();m=rd(); 68 for(rg int i=1;i<=n;i++)gt(),ins(); 69 get_fl(); 70 for(rg int i=1;i<=m;i++){ 71 gt(); 72 memset(f,0,sizeof(bool)*(len+1)); 73 ans = 0; 74 query(); 75 printf("%d\n",ans); 76 } 77 return 0; 78 }
- 3.bzoj1030[Jsoi2007]文本生成器 (bzoj3530类似)
- 如果直接问一个已知的文本是否含有给出单词就是模板题了;
- 同样我们只关心给出的模板串,对模板建自动机做dp
- $dp[i][j]$表示生成了前$i$位,当前匹配状态为自动机的$j$号节点;
- 新开一个节点统计答案,每次都自乘*26;
- 枚举下一个字符k,考虑转移到ch[j][k] , 注意如果ch[j][k]是一个已经匹配了单词的节点就直接转移到统计答案的节点
- 初始f[0][0]=1;
- O(N*Len)某些情况下可写成矩阵乘法转移
- 我写的时候犯了点错:
-
但是ac自动机的匹配算法中,注意所有匹配走过路径的节点并不是所有的单词节点,因为可能会有中间匹配点可以按照失配边走到有串的节点,所以代码里vis要沿fail向下传递;
1 #include<bits/stdc++.h> 2 #define rg register 3 #define il inline 4 using namespace std; 5 const int N=61,M=110,mod=10007; 6 int n,m,ch[N*M][26],f[M][N*M],vis[N*M],fl[N*M],head,tail,q[N*M],sz; 7 char s[M]; 8 il void ins(){ 9 int l = strlen(s) , u=0; 10 for(rg int i=0;i<l;i++){ 11 int& v = ch[u][s[i]-'A']; 12 if(!v)v = ++sz; 13 u = v; 14 } 15 vis[u]=1; 16 } 17 void get_fl(){ 18 for(int i=0;i<26;i++)if(ch[0][i])q[++tail]=ch[0][i]; 19 while(head<tail){ 20 int u = q[++head]; 21 for(rg int i=0;i<26;i++){ 22 int& v=ch[u][i]; 23 if(!v){v=ch[fl[u]][i];continue;} 24 fl[v]=ch[fl[u]][i]; 25 q[++tail]=v; 26 } 27 } 28 for(rg int i=1;i<=tail;i++)vis[q[i]] |= vis[fl[q[i]]]; 29 } 30 il void upd(int&x,int y){x+=y;if(x>=mod)x-=mod;} 31 int main(){ 32 freopen("bzoj1030.in","r",stdin); 33 freopen("bzoj1030.out","w",stdout); 34 scanf("%d%d",&n,&m); 35 for(rg int i=1;i<=n;i++){scanf("%s",s);ins();} 36 get_fl(); 37 f[0][0]=1; 38 for(rg int i=0;i<m;i++){ 39 for(rg int j=0;j<=sz;j++){ 40 for(rg int k=0;k<26;k++){ 41 if(vis[ch[j][k]])upd(f[i+1][sz+1],f[i][j]); 42 else upd(f[i+1][ch[j][k]],f[i][j]); 43 } 44 } 45 upd(f[i+1][sz+1],f[i][sz+1]*26%mod); 46 } 47 cout<<f[m][sz+1]<<endl; 48 return 0; 49 }
- 4.bzoj1444[Jsoi2009]有趣的游戏
- https://www.cnblogs.com/clrs97/p/4987277.html ORZ
- 不妨吧trie树上的有单词的节点叫做关键点,其他是非关键点;
- 这题有意思的地方在于只能在关键点停下来;
- 直接的想法是:用自动机建立概率方程,每个点的概率等于所有指向它的节点贡献之和,同时关键点不让转移(即不贡献出边);
- 然而这样也没有常数项。。。。。。。解出来都是0???!接下来的操作比较神奇:
- 把0号点的方程(也有人说随意那个)用所有关键点概率之和==0替换再高斯消元即可,小心会输出-0.00所以要特判一下;
- (如果有更好的理解希望指点,感激不尽)
1 #include<bits/stdc++.h> 2 #define ld double 3 #define rg register 4 #define il inline 5 using namespace std; 6 const int N=110; 7 int n,l,m,sz,id[N],vis[N],fl[N],q[N],t,w,ch[N][26],px[N],py[N]; 8 ld p[N],a[N][N]; 9 char s[N]; 10 void get_fl(){ 11 for(int i=0;i<m;i++)if(ch[0][i])q[++w]=ch[0][i]; 12 while(t<w){ 13 int u = q[++t]; 14 for(int i=0;i<m;i++){ 15 int&v = ch[u][i]; 16 if(!v){v=ch[fl[u]][i];continue;} 17 fl[v]=ch[fl[u]][i]; 18 q[++w]=v; 19 } 20 } 21 a[0][sz+1]=1; 22 for(int i=0;i<=sz;i++){ 23 if(i)a[i][i]=1; 24 if(vis[i]){a[0][i]=1;continue;} 25 for(int j=0;j<m;j++)if(px[j]){ 26 int v = ch[i][j]; 27 if(v)a[v][i] -= p[j]; 28 } 29 } 30 } 31 void gauss(){ 32 for(rg int i=0;i<=sz;i++){ 33 int pos=i; 34 for(rg int j=i+1;j<=sz;j++)if(fabs(a[j][i])>fabs(a[pos][i]))pos=j; 35 if(pos!=i)for(rg int j=i;j<=sz+1;j++)swap(a[i][j],a[pos][j]); 36 for(rg int j=i+1;j<=sz;j++){ 37 ld tmp = a[j][i] / a[i][i]; 38 for(rg int k=i;k<=sz+1;k++)a[j][k] -= tmp * a[i][k]; 39 } 40 } 41 for(rg int i=sz;~i;i--){ 42 for(rg int j=i+1;j<=sz;j++)a[i][sz+1] -= a[j][sz+1] * a[i][j]; 43 a[i][sz+1] /= a[i][i]; 44 } 45 } 46 int main(){ 47 freopen("bzoj1444.in","r",stdin); 48 freopen("bzoj1444.out","w",stdout); 49 scanf("%d%d%d",&n,&l,&m); 50 for(int i=0;i<m;i++){ 51 scanf("%d%d",&px[i],&py[i]); 52 p[i] = 1.0 * px[i] / py[i]; 53 } 54 for(int i=1;i<=n;i++){ 55 scanf("%s",s); 56 int u=0; 57 for(int j=0;j<l;j++){ 58 if(!ch[u][s[j]-'A'])ch[u][s[j]-'A']=++sz; 59 u = ch[u][s[j]-'A']; 60 } 61 id[i] = u; vis[u] = 1; 62 } 63 get_fl(); 64 gauss(); 65 for(int i=1;i<=n;i++){ 66 ld x = fabs(a[id[i]][sz+1]); 67 // x = fabs(-0.00); 68 if(x>0)printf("%.2lf\n",x); 69 else puts("0.00"); 70 } 71 return 0; 72 }
- 5.bzoj2434[Noi2011]阿狸的打字机
- 模拟操作建自动机,考虑现在有一颗trie树和fail树
- x在y里面出现的次数 == 在trie树上y的所有祖先 在x在fail树上的子树里的有多少个
- 维护这可以直接在trie上dfs,树状数组区间修改维护当前点到trie的根再fail树的dfs序,查询子树信息;
- 或者直接对trie树链剖建主席树,权值是fail树的dfs序;
1 #include<bits/stdc++.h> 2 #define rg register 3 #define il inline 4 #define pb push_back 5 using namespace std; 6 const int N=100010,M=20; 7 int n,m,ch[N][26],fa[N],tot,cnt,que[N],head,tail,fl[N],a[N]; 8 int id[N],pos[N],st[N],ed[N],sum[N*M],sz,tp[N],idx,son[N],size[N],rt[N],ls[N*M],rs[N*M]; 9 vector<int>g[N]; 10 char s[N]; 11 il char gc(){ 12 static char *p1,*p2,s[1000000]; 13 if(p1==p2)p2=(p1=s)+fread(s,1,1000000,stdin); 14 return(p1==p2)?EOF:*p1++; 15 } 16 il int rd(){ 17 int x=0; char c=gc(); 18 while(c<'0'||c>'9')c=gc(); 19 while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+c-'0',c=gc(); 20 return x; 21 } 22 il int gt(){ 23 char*p = s , c = gc(); 24 while(!isalpha(c))c=gc(); 25 while(isalpha(c))*p++=c,c=gc(); 26 return p - s; 27 } 28 void get_fl(){ 29 for(int i=0;i<26;i++)if(ch[0][i]){ 30 que[++tail]=ch[0][i]; 31 g[0].pb(ch[0][i]); 32 } 33 while(head<tail){ 34 int u=que[++head]; 35 for(int i=0;i<26;i++){ 36 int& v=ch[u][i]; 37 if(!v){v=ch[fl[u]][i];continue;} 38 fl[v]=ch[fl[u]][i]; 39 g[fl[v]].pb(v); 40 que[++tail] = v; 41 } 42 } 43 } 44 void dfs1(int u){ 45 size[u]=1;son[u]=0; 46 for(int i=0;i<26;i++){ 47 int v = ch[u][i]; 48 if(!v||v==fa[u])continue; 49 dfs1(v); 50 size[u]+=size[v]; 51 if(!son[u]||size[v]>size[son[u]])son[u]=v; 52 } 53 } 54 void dfs2(int u,int top){ 55 a[pos[u]=++idx]=u; 56 tp[u]=top; 57 if(son[u])dfs2(son[u],top); 58 for(int i=0;i<26;i++){ 59 int v=ch[u][i]; 60 if(!v||v==fa[u]||v==son[u])continue; 61 dfs2(v,v); 62 } 63 } 64 void dfs(int u){ 65 st[u] = ++idx; 66 for(int i=0;i<(int)g[u].size();i++){ 67 int v = g[u][i]; 68 dfs(v); 69 } 70 ed[u] = idx; 71 } 72 il void ins(int&k,int lst,int l,int r,int x,int y){ 73 sum[k=++sz]=sum[lst]+y; 74 ls[k]=ls[lst];rs[k]=rs[lst]; 75 if(l==r)return ; 76 int mid=(l+r)>>1; 77 if(x<=mid)ins(ls[k],ls[lst],l,mid,x,y); 78 else ins(rs[k],rs[lst],mid+1,r,x,y); 79 } 80 il int query(int k,int lst,int l,int r,int x,int y){ 81 if(l==x&&r==y)return sum[k]-sum[lst]; 82 else{ 83 int mid=(l+r)>>1; 84 if(y<=mid)return query(ls[k],ls[lst],l,mid,x,y); 85 else if(x>mid)return query(rs[k],rs[lst],mid+1,r,x,y); 86 else return query(ls[k],ls[lst],l,mid,x,mid)+query(rs[k],rs[lst],mid+1,r,mid+1,y); 87 } 88 } 89 il int Query(int x,int y){ 90 int re=0; 91 while(~x){ 92 re += query(rt[pos[x]],rt[pos[tp[x]]-1],1,cnt,st[y],ed[y]); 93 x = fa[tp[x]]; 94 } 95 return re; 96 } 97 int main(){ 98 freopen("bzoj2434.in","r",stdin); 99 freopen("bzoj2434.out","w",stdout); 100 n=gt(); 101 int u = 0; 102 for(rg int i=0;i<n;i++){ 103 if(s[i]=='B')u = fa[u]; 104 else if(s[i]=='P')id[++tot] = u; 105 else { 106 if(!ch[u][s[i]-'a'])ch[u][s[i]-'a']=++cnt; 107 fa[ch[u][s[i]-'a']] = u; 108 u = ch[u][s[i]-'a']; 109 } 110 } 111 fa[0]=-1; 112 dfs1(0); 113 dfs2(0,0); 114 get_fl(); 115 idx=0;dfs(0); 116 cnt++; 117 for(rg int i=1;i<=cnt;i++){ins(rt[i],rt[i-1],1,cnt,st[a[i]],1);} 118 m = rd(); 119 for(rg int i=1,x,y;i<=m;i++){ 120 x = rd() , y=rd(); 121 int ans = Query(id[y],id[x]); 122 printf("%d\n",ans); 123 } 124 return 0; 125 }