zoukankan      html  css  js  c++  java
  • 【学习笔记】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
        ",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
        bzoj3172
      • 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
        ",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向下传递;

         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 }
        bzoj1030
      • 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
        ",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序;
          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
        ",ans);
        123     }
        124     return 0;
        125 }
        bzoj2434
  • 相关阅读:
    JavaScript语言和jQuery技术--2
    Mybatis框架--1
    数据接口请求异常:parsererror
    Callable和Future?
    如何实现拦截器?
    window.onload()函数和jQuery中的document.ready()有什么区别?
    SpringBoot框架 1.什么是 Spring Boot?
    什么是 JavaConfig?
    jquery中$.get()提交和$.post()提交有区别吗?
    什么是线程?
  • 原文地址:https://www.cnblogs.com/Paul-Guderian/p/10217205.html
Copyright © 2011-2022 走看看