【学习笔记】ac自动机&fail树

摘要:
如果有精通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]$,避免暴力往上跳;1intn,len,ch[N][26],sz,cnt[N],fl[N];2chars[N];3queueq;4voidins(){5intu=0;6for{7c=s[i]-'a';8if(!
  • 定义
    • 解决文本串和多个模式串匹配的问题;
    • 本质是由多个模式串形成的一个字典树,由tie的意义知道:trie上的每一个节点都是一个模式串的前缀;
    • 在trie上加入fail边,一个节点fail边指向这个节点所代表的前缀的最长后缀节点(除开自身的后缀);
    • 也就是说如果x->y,那么y所代表的串是x所代表的串在trie上出现过的最大后缀;
  • 例子
    • (黑边为trie,红边为fail)
    • 以"hers","she","his","i"为例:
    • 【学习笔记】ac自动机&fail树第1张
    • 原谅我的画图水平。。。如果有精通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 chars[N];
      3 queue<int>q;
      4 voidins(){
      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 voidget_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边形成了一个数的结构;
    • 【学习笔记】ac自动机&amp;fail树第2张
    • 沿着u的fail祖先往上走会找到u节点的所有后缀节点;
    • 对于字符串s,在自动机里匹配到的所有节点的所有fail祖先就表示s的所有子串;
  • 习题
    • 1.bzoj3172[Tjoi2013]单词
    • ac自动机模板,由于trie树上面只存储了前缀,子串的出现次数要按照深度从大到小对每个点向fail指针累加cnt;
      【学习笔记】ac自动机&amp;fail树第3张【学习笔记】ac自动机&amp;fail树第4张
      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 namespacestd;
      23 const int N=1000010;
      24 int n,ch[N][26],sz,fl[N],sum[N],ans[N],id[N];
      25 chars[N];
      26 intq[N],t,w;
      27 void ins(intnow){
      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 voidsolve(){
      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
      ",sum[id[i]]);
      48 }
      49 intmain(){
      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
      bzoj3172
    • 2.bzoj1212[Hnoi2004]L语言
    • n,m都很小;$f[i]$表示前缀i是否可以被理解,$f[i]$可以从$f[j](j<i)$转移的条件是$s[j+1]---S[i]$可以被理解,暴力判断应该会T
    • 对字典建自动机,如果把文章跑一遍,可以直接用fail指针暴力往上跳就可以找到所有的可以转移的j;
      【学习笔记】ac自动机&amp;fail树第5张【学习笔记】ac自动机&amp;fail树第6张
      1 #include<bits/stdc++.h>
      2 #define rg register
      3 #define il inline
      4 using namespacestd;
      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 boolf[N];
      8 chars[N];
      9 il chargc(){
      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 intrd(){
      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     returnx;
      19 }
      20 il voidgt(){
      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 voidins(){
      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 voidget_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,intpos){
      52     if(!x)return false;
      53     if(vis[x]&&f[pos-dep[x]])return true;
      54     returnfind(lst[x],pos);
      55 } 
      56 voidquery(){
      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 intmain(){
      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
      ",ans);
      76 }
      77     return 0;
      78 }
      bzoj1212
    • 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向下传递;

      【学习笔记】ac自动机&amp;fail树第7张【学习笔记】ac自动机&amp;fail树第8张
      1 #include<bits/stdc++.h>
      2 #define rg register
      3 #define il inline
      4 using namespacestd;
      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 chars[M];
      8 il voidins(){
      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 voidget_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 intmain(){
      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 }
      bzoj1030
    • 4.bzoj1444[Jsoi2009]有趣的游戏
    • https://www.cnblogs.com/clrs97/p/4987277.html ORZ
    • 不妨吧trie树上的有单词的节点叫做关键点,其他是非关键点;
    • 这题有意思的地方在于只能在关键点停下来;
    • 直接的想法是:用自动机建立概率方程,每个点的概率等于所有指向它的节点贡献之和,同时关键点不让转移(即不贡献出边);
    • 然而这样也没有常数项。。。。。。。解出来都是0???!接下来的操作比较神奇:
    • 把0号点的方程(也有人说随意那个)用所有关键点概率之和==0替换再高斯消元即可,小心会输出-0.00所以要特判一下;
    • (如果有更好的理解希望指点,感激不尽)
      【学习笔记】ac自动机&amp;fail树第9张【学习笔记】ac自动机&amp;fail树第10张
      1 #include<bits/stdc++.h>
      2 #define ld double
      3 #define rg register
      4 #define il inline
      5 using namespacestd;
      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 chars[N]; 
      10 voidget_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 voidgauss(){
      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 intmain(){
      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
      ",x);
      69         else puts("0.00");
      70 }
      71     return 0;
      72 }
      bzoj1444
    • 5.bzoj2434[Noi2011]阿狸的打字机
    • 模拟操作建自动机,考虑现在有一颗trie树和fail树
    • x在y里面出现的次数 == 在trie树上y的所有祖先 在x在fail树上的子树里的有多少个
    • 维护这可以直接在trie上dfs,树状数组区间修改维护当前点到trie的根再fail树的dfs序,查询子树信息;
    • 或者直接对trie树链剖建主席树,权值是fail树的dfs序;
      【学习笔记】ac自动机&amp;fail树第11张【学习笔记】ac自动机&amp;fail树第12张
      1 #include<bits/stdc++.h>
      2 #define rg register
      3 #define il inline 
      4 #define pb push_back
      5 using namespacestd;
      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 chars[N];
      11 il chargc(){
      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 intrd(){
      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     returnx;
      21 }
      22 il intgt(){
      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 voidget_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(intu){
      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,inttop){
      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(intu){
      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,inty){
      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,inty){
      81     if(l==x&&r==y)return sum[k]-sum[lst];
      82     else{
      83         int mid=(l+r)>>1;
      84         if(y<=mid)returnquery(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,inty){
      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     returnre;
      96 }
      97 intmain(){
      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
      ",ans);
      123 }
      124     return 0;
      125 }
      bzoj2434

免责声明:文章转载自《【学习笔记】ac自动机&amp;amp;fail树》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇git 的安装以及使用:是一个开源的分布式版本控制系统,可以对项目进行版本管理。 早期是linux之父用来管理linux系统源代码的(linux是和windows一样操作系统 开源免费的操作Java常用快捷键下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

USACO 1.3 Wormholes

Wormholes Farmer John's hobby of conducting high-energy physics experiments on weekends has backfired, causing N wormholes (2 <= N <= 12, N even) to materialize on his farm,...

LCA问题【RMQ+Tarjan】

LCA-求树上两点最近公共祖先问题 lrj的紫书上提供了一种将LCA问题转化为RMQ问题的方法,即dfs一次处理出一个序列,first(u)代表u第一次出现的下标,则对于u,v的最近公共祖先的下标即为RMQ(first(u), first(v))。 LCA->RMQ(在线处理): 1 #include<bits/stdc++.h> 2...

[js]使用百度编辑器uediter时遇到的一些问题(span,div等被过滤)

在使用uediter编辑html代码的时候,div,span等标签会莫名其妙的被过滤掉,然后上网查资料,改了点配置: 1:在ueiter.all.js中找到allowDivTransToP me.setOpt({ 'allowDivTransToP':true, 'disabledTableInTable':true...

ISR吞吐性能问题

ISR大致可以分几类: Cisco 860、880、890 ISR1800 (fixed)、1800 (modular)、2800、3800 Series ISR1900、2900、3800、3900 Series ISR4K 每一代的设备,设备的性能肯定都不一样,本摘要,将主要记录ISR的吞吐性能问题。 1、下图主要体现的是800 Series和19...

最小有向生成树

先看一下lrj的大白书上的讲解 emm。。。我是看完之后直接看的模板题代码。。。居然看懂。。。行吧。。 就是先判断 能不能联通  如能联通 就求出每个点的最小前驱边  求完之后 看有没有环 如有环 缩点更新 然后一直重复 直至无环且联通。。   //邻接矩阵模板: #include <iostream> #include <cstdio...

Kurento KMS 流媒体服务器 webRTC转rtmp http flv

webRTC ==(通过nodejs指定的sdp,这个sdp写的要与webrtc源一致)==》 RTP ==》RTMP 各种推流方法:https://www.cnblogs.com/bigben0123/p/14188475.html 整个启动流程: 1,启动kurento服务: ~/kms/kms-omni-build$ ./bin/kms-build-...