AC自动机模板以及简单的入门题总结

AC自动机模板

//AC自动机解决一类文本串匹配多关键字的问题
//fail就是Trie上的next,当失配时直接跳转到下一个节点,继续匹配
//可以用Fail的递归来传递敏感串的状态,
//insert_()前要init(),query_()之前要build().
//要充分利用end_数组来存储状态
//AC自动机其实就是长度为k的字符串转移到长度为k+1的串的转移状态
//若当前共有cnt个节点,则转移共有cnt*MAX个,MAX表示下一个字符可能的个数
//对于未出现过的文本串,直接转移回root,否则转移到下一位
//fail[i] 表示作为节点为i的串的后缀中在Trie上出现过的前缀
const int maxn = 5e5+5;//64MB可以开到2e6*30,尽量开大
struct ACTrie
{
    int tree[maxn][26],fail[maxn],end_[maxn];//end_数组一般表示是否为结束字符
    int root,cnt;
    int newnode()
    {
        for(int i=0;i<26;i++)
            tree[cnt][i]=-1;
        end_[cnt++]=0;
        return cnt-1;
    }
    void init()
    {
        cnt=0;
        root=newnode();
    }
    void insert_(char str[])
    {
        int len= strlen(str);
        int pos=root;
        for(int i=0;iint id=str[i]-'a';
            if(tree[pos][id]==-1)
                tree[pos][id]=newnode();
            pos=tree[pos][id];
        }
        end_[pos]++;
    }
    void build()
    {
        queue<int> que;
        fail[root]=root;
        for(int i=0;i<26;i++)
        {
            if(tree[root][i]==-1) tree[root][i]=root;
            else
            {
                fail[tree[root][i]]=root;
                que.push(tree[root][i]);
            }
        }
        while(!que.empty())
        {
            int now=que.front();
            que.pop();
            for(int i=0;i<26;i++)
            {
                if(tree[now][i]==-1)
                    tree[now][i]=tree[fail[now]][i];
                else
                {
                    fail[tree[now][i]]=tree[fail[now]][i];
                    que.push(tree[now][i]);
                }
            }
        }
    }
    int query(char str[])
    {
        int len=strlen(str);
        int now=root;
        int res=0;
        for(int i=0;i'a'];
            int temp=now;
            while(temp!=root)//类似kmp,不断递归寻找作为后缀出现过的前缀,计算贡献
            {
                res+=end_[temp];
                end_[temp]=0;//当每个单词只统计一次而且只有一个查询,重置为0,
                temp=fail[temp];
            }
        }
        return res;
    }
};

AC自动机第一题

HDU2222
题意就是统计一个文本串中出现过多少个给定的字符串,每个给定的字符串只统计一次。
这就是AC自动机的裸题,我们直到AC自动机的精髓在于状态转移,我们可以从k状态转移到k+1的状态,所以我们只需要把文本串放到AC自动机上面沿着Trie树跑,然后用Fail来递归寻找出现过的前缀,统计贡献就好了,注意统计贡献的时候要将贡献清空,否则一个字符串将统计多次
HDU2222代码

#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 5e5+5;
struct ACTrie
{
    int tree[maxn][26],fail[maxn],end_[maxn];
    int root,cnt;
    int newnode()
    {
        for(int i=0;i<26;i++)
            tree[cnt][i]=-1;
        end_[cnt++]=0;
        return cnt-1;
    }
    void init()
    {
        cnt=0;
        root=newnode();
    }
    void insert_(char str[])
    {
        int len= strlen(str);
        int pos=root;
        for(int i=0;iint id=str[i]-'a';
            if(tree[pos][id]==-1)
                tree[pos][id]=newnode();
            pos=tree[pos][id];
        }
        end_[pos]++;
    }
    void build()
    {
        queue<int> que;
        fail[root]=root;
        for(int i=0;i<26;i++)
        {
            if(tree[root][i]==-1) tree[root][i]=root;
            else
            {
                fail[tree[root][i]]=root;
                que.push(tree[root][i]);
            }
        }
        while(!que.empty())
        {
            int now=que.front();
            que.pop();
            for(int i=0;i<26;i++)
            {
                if(tree[now][i]==-1)
                    tree[now][i]=tree[fail[now]][i];
                else
                {
                    fail[tree[now][i]]=tree[fail[now]][i];
                    que.push(tree[now][i]);
                }
            }
        }
    }
    int query(char str[])
    {
        int len=strlen(str);
        int now=root;
        int res=0;
        for(int i=0;i'a'];//在Trie上不断向后跑
            int temp=now;
            while(temp!=root)
            {
                res+=end_[temp];
                end_[temp]=0;
                temp=fail[temp];
            }
        }
        return res;
    }
};
char str[maxn*2];
ACTrie ac;
int main()
{
    int t,n;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        ac.init();
        for(int i=0;iscanf("%s",str);
            ac.insert_(str);
        }
        ac.build();
        scanf("%s",str);
        printf("%d\n",ac.query(str));
    }
    return 0;
}

AC自动机第二题

HDU2896
题意就是给你一些病毒网站的关键字,再给你一些网站链接,问每个网站出现过哪些病毒网站关键字
我们只需要用病毒网站关键字构建出AC自动机,之后对每个网站开一个set存储与见过的病毒网站关键字下标就可以了,注意本题字符为ASCII码可见字符,所以tree[][]数组第二维要开到128
HDU2896代码

#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 5e5+5;
struct ACTrie
{
    int tree[maxn][128],fail[maxn],end_[maxn];
    int root,cnt,num;
    set<int> s;
    int newnode()
    {
        for(int i=0;i<128;i++)
            tree[cnt][i]=-1;
        end_[cnt]=0;
        return cnt++;
    }
    void init()
    {
        cnt=0;
        num=0;
        root=newnode();
    }
    void insert_(char str[])
    {
        int len=strlen(str);
        int pos=root;
        for(int i=0;iint id=str[i];
            if(tree[pos][id]==-1) tree[pos][id]=newnode();
            pos=tree[pos][id];
        }
        end_[pos]=++num;
    }
    void build()
    {
        queue<int> que;
        fail[root]=root;
        for(int i=0;i<128;i++)
        {
            if(tree[root][i]==-1) tree[root][i]=root;
            else
            {
                fail[tree[root][i]]=root;
                que.push(tree[root][i]);
            }
        }
        while(!que.empty())
        {
            int now=que.front();
            que.pop();
            for(int i=0;i<128;i++)
            {
                if(tree[now][i]==-1)
                    tree[now][i]=tree[fail[now]][i];
                else
                {
                    fail[tree[now][i]]=tree[fail[now]][i];
                    que.push(tree[now][i]);
                }
            }
        }
    }
    void query(char str[])
    {
        s.clear();
        int len=strlen(str);
        int now=root;
        int res=0;
        for(int i=0;iint temp=now;
            while(temp!=root)//对每一个前缀串递归统计是否有后缀是病毒串
            {
                if(end_[temp]!=0) s.insert(end_[temp]);
                temp=fail[temp];
            }
        }
        return ;
    }
};
ACTrie ac;
char str[10005];
int main()
{
    int n,m;
    int flag=0;
    scanf("%d",&n);
    ac.init();
    while(n--)
    {
        scanf("%s",str);
        ac.insert_(str);
    }
    ac.build();
    scanf("%d",&m);
    int cnt=0;
    int ans=0;
    while(m--)
    {
        scanf("%s",str);
        ac.query(str);
        cnt++;
        if(ac.s.size()!=0)
        {
            ans++;
            set<int>::iterator it;
            printf("web %d:",cnt);
            for(it=ac.s.begin();it!=ac.s.end();++it)
            {
                printf(" %d",*it);
            }
            printf("\n");
        }
    }
    printf("total: %d\n",ans);
   return 0;
}

AC自动机第三题

HDU3065
题意就是给你一些病毒网站关键字,统计每个关键字的出现次数,可重叠。
我们用病毒网站关键字搭建好AC自动机,用文本串在上面跑,由于每个Fail递归的过程是必包含当前后缀的(仔细思考),所以是不会出现重复统计的情况的,所以我们只需要在递归的时候对每个关键字计数,最后输出就可以了。
注意:多组输入,ASCII码可见字符有128种
HDU3065代码

#include
#include
#include
#include
#include
using namespace std;
const int maxn = 5e5+5;
struct ACTrie
{
    int tree[maxn][128],fail[maxn],end_[maxn],sum[maxn];//用end_数组存储该单词的下标,sum统计出现次数
    int root,cnt,num;
    int newnode()
    {
        for(int i=0;i<128;i++)
            tree[cnt][i]=-1;
        end_[cnt]=0;
        return cnt++;
    }
    void init()
    {
        cnt=0;
        num=0;
        root=newnode();
        return ;
    }
    void insert_(char str[])
    {
        int len=strlen(str);
        int pos=root;
        for(int i=0;iint id=str[i];
            if(tree[pos][id]==-1) tree[pos][id]=newnode();
            pos=tree[pos][id];
        }
        end_[pos]=++num;//用时间戳记录下标
        sum[num]=0;
        return ;
    }
    void build()
    {
        queue<int> que;
        fail[root]=root;
        for(int i=0;i<128;i++)
        {
            if(tree[root][i]==-1) tree[root][i]=root;
            else
            {
                fail[tree[root][i]]=root;
                que.push(tree[root][i]);
            }
        }
        while(!que.empty())
        {
            int now=que.front();
            que.pop();
            for(int i=0;i<128;i++)
            {
                if(tree[now][i]==-1) tree[now][i]=tree[fail[now]][i];
                else
                {
                    fail[tree[now][i]]=tree[fail[now]][i];
                    que.push(tree[now][i]);
                }
            }
        }
        return ;
    }
    void query(char str[])
    {
        int len=strlen(str);
        int now=root;
        for(int i=0;iint tmp=now;
            while(tmp!=root)
            {
                if(end_[tmp]) sum[end_[tmp]]++;//统计答案
                tmp=fail[tmp];
            }
        }
        return ;
    }
};
char str[1005][55];
ACTrie ac;
char str0[2000005];
int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
         ac.init();
        for(int i=1;i<=n;i++)
        {
            scanf("%s",str[i]);
            ac.insert_(str[i]);
        }
        ac.build();
        scanf("%s",str0);
        ac.query(str0);
        for(int i=1;i<=ac.num;i++)
        {
            if(ac.sum[i]!=0)
            printf("%s: %d\n",str[i],ac.sum[i]);
        }
    }
    return 0;
}

AC自动机第四题

ZOJ3430
题意就是给你一个加密的单词,加密方式就是先把字符按照ASCII表转为2进制,再截取6个为一段转为10进制,再去表中改为对应字符。
我们只需要把所有关键字和文本串都解码之后,就是一个最朴素的AC自动机了。
密文转换方法是向kuangbin大神学习的,我们通过思考可以发现,解码的过程就是把四个字符转换为3个字符的过程,所以我们只要对加密串进行一些二进制操作,就可以直接将24位二进制码分为3个八位二进制码。具体过程请参考代码。
注意此题字符可能有256种,所以要用unsigned char
而且 ZOJ RE和MLE均返回 Segmentation Fault ,此题卡内存比较严重,需要好好计算一下
ZOJ3430代码

#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn =5e4+5;
unsigned char aa[maxn];
#define dbg(x) cout<<#x<<" "<
struct ACTrie
{
  int tree[maxn][256], fail[maxn],end_[maxn];
  int root,cnt,num;
  set<int> s;
  int newnode()
  {
      for(int i=0;i<256;i++)
        tree[cnt][i]=-1;
      end_[cnt]=0;
      return cnt++;
  }
  void init()
  {
      cnt=0;
      num=0;
      root=newnode();
  }
  void insert_(unsigned char str[],int len)
  {
      int pos=root;
      for(int i=0;iint id=str[i];
          if(tree[pos][id]==-1) tree[pos][id]=newnode();
          pos=tree[pos][id];
      }
      end_[pos]=++num;
  }
  void build()
  {
      queue<int> que;
      fail[root]=root;
      for(int i=0;i<256;i++)
      {
          if(tree[root][i]==-1) tree[root][i]=root;
          else
          {
              fail[tree[root][i]]=root;
              que.push(tree[root][i]);
          }
      }
      while(!que.empty())
      {
          int now=que.front();
          que.pop();
          for(int i=0;i<256;i++)
          {
              if(tree[now][i]==-1) tree[now][i]=tree[fail[now]][i];
              else
              {
                  fail[tree[now][i]]=tree[fail[now]][i];
                  que.push(tree[now][i]);
              }
          }
      }
  }
  int query(unsigned char *str,int len)
  {
       s.clear();
        int now=root;
        int res=0;
        for(int i=0;iint id=(int)str[i];
            now=tree[now][id];
            int temp=now;
            while(temp!=root)
            {
                if(end_[temp]!=0) s.insert(end_[temp]);
                temp=fail[temp];
            }
        }
        return (int)s.size();
  }
};
unsigned char cal(char c)
{
    if(c>='A'&&c<='Z')
    {
        return c-'A';
    }
    if(c>='a'&&c<='z')
    {
        return c-'a'+26;
    }
    if(c>='0'&&c<='9')
    {
        return c-'0'+52;
    }
    if(c=='+')  return 62;
    else return 63;
}
int change(unsigned char str[],int len)
{
    int t=0;
    for(int i=0;i4)//每四个字符计算一次,转换为三个字符
    {
        aa[t++]=((str[i]<<2)|(str[i+1]>>4));//截取第一个字符的全部+第二个字符前两位
        if(i+2 < len)
            aa[t++]=( (str[i+1]<<4)|(str[i+2]>>2) );//截取第二个字符后四位+第三个字符前四位
        if(i+3 < len)
            aa[t++]= ( (str[i+2]<<6)|str[i+3] );//截取第三个字符后两位+第四个字符全部
    }
    return t;
}
ACTrie ac;
char str[maxn];
unsigned char ss[maxn];
int main()
{
    int n,m;
    while(scanf("%d",&n)!=EOF)
    {
        ac.init();
        while(n--)
        {
            scanf("%s",str);
            int len=strlen(str);
            while(str[len-1]=='=') len--;
            for(int i=0;iint len2 = change(ss,len);
            ac.insert_(aa,len2);//转换之后插入AC自动机
        }
        ac.build();
        scanf("%d",&m);
        while(m--)
        {
            //gets(str);
            scanf("%s",str);
            int len=strlen(str);
            while(str[len-1]=='=') len--;
            for(int i=0;iint len2 = change(ss,len);
            printf("%d\n",ac.query(aa,len2));
        }
        printf("\n");
    }
    return 0;
}

AC自动机第五题

POJ2778
本题题意是给你一个字符集和一个长度m,还有一些敏感串,求出字符集构造出的长度为m的字符串中不包含敏感串的串的个数。
我们用到AC自动机的性质,想象一下如果从len=k向len=k+1转移,AC自动机上每个状态之间有多少种转移方法,就可以构造出对应的转移矩阵,再利用矩阵快速幂就可以求解。在构造转移矩阵的时候,要注意如果某串的后缀是敏感串,也是不可以转移的。
转移矩阵 Tn T n T[i][j] T [ i ] [ j ] 即表示i状态转移n次到达j状态的方案数,所以最后统计第一行的和,也就是所有初始状态转移n次得到的状态之和了。
你可能会想到一些原本不在AC自动机上面的状态是不是没有被算,其实所有不在AC自动机上的状态都是不被约束的状态,他们是和从root开始的状态相同的,我们在构建fail的时候就把这些串下标都存为root,所以直接算转移矩阵的时候直接转移就好了。(慢慢理解,慢慢理解
POJ2778代码

#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define  dbg(x) cout<<#x<<" "<
typedef unsigned long long ll;
const int maxn = 2e6+5;
const int Mod=100000;
map<char,int> mp;
struct ACTrie
{
    int tree[maxn][4],fail[maxn];
    int end_[maxn];
    int root,num,cnt;
    int newnode()
    {
        for(int i=0;i<4;i++)
            tree[cnt][i]=-1;
        end_[cnt]=0;
        return cnt++;
    }
    void init()
    {
        cnt=0;
        num=0;
        root=newnode();
    }
    void insert_(char str[])
    {
        int pos=root;
        int len=strlen(str);
        for(int i=0;iint id=mp[str[i]];
            if(tree[pos][id]==-1) tree[pos][id]=newnode();
            pos=tree[pos][id];
        }
        end_[pos]=1;
    }
    void build()
    {
        queue<int> que;
        fail[root]=root;
        for(int i=0;i<4;i++)
        {
            if(tree[root][i]==-1) tree[root][i]=root;
            else
            {
                fail[tree[root][i]]=root;
                que.push(tree[root][i]);
            }
        }
        while(!que.empty())
        {
            int now=que.front();
            que.pop();
            for(int i=0;i<4;i++)
            {
                if(tree[now][i]==-1) tree[now][i]=tree[fail[now]][i];
                else
                {
                    fail[tree[now][i]]=tree[fail[now]][i];
                    que.push(tree[now][i]);
                }
                end_[tree[now][i]]|=end_[tree[fail[now]][i]];//注意这个过程,若该字符串的后缀为病毒串,则该字符串也是病毒串。
            }
        }
    }
};
ACTrie ac;
struct mat
{
    ll jz[110][110];
};
mat make_mat()
{
    mat res;
    memset(res.jz,0,sizeof(res.jz));
    for(int i=0;iif(ac.end_[i]) continue;//转移之前为病毒串不统计
        for(int j=0;j<4;j++)
        {
            if(ac.end_[ac.tree[i][j]]) continue;//转移之后为病毒串不统计
            ++res.jz[i][ac.tree[i][j]];
        }
    }
    return res;
}
mat mat_mul(mat x,mat y)
{
    mat res;
    memset(res.jz,0,sizeof(res.jz));
    for(int i=0;ifor(int j=0;jfor(int k=0;kreturn res;
}
ll power_mod (ll b)//.res是系数矩阵,ans是变换矩阵左->ans,右->res.
{
    mat ans,res;
    res=make_mat();
    memset(ans.jz,0,sizeof(ans.jz));
    for(int i=0;i1;
    while(b>0)
    {
        if(b&1)  ans=mat_mul(res,ans);//所以应该系数矩阵在前ans,res);
        b=b>>1;
        res=mat_mul(res,res);
    }
    ll tmp=0;
    for(int i=0;i0][i]%Mod)%Mod;
    return tmp;//返回指定位置元素
}
char str[11];
int main()
{
    mp['A']=0;
    mp['C']=1;
    mp['T']=2;
    mp['G']=3;
    int n;
    ll m;
    while(scanf("%d%I64d",&n,&m)!=EOF)
    {
        ac.init();
        while(n--)
        {
            scanf("%s",str);
            ac.insert_(str);
        }
        ac.build();
        printf("%I64d\n",power_mod(m));
    }
    return 0;
}

AC自动机第六题

HDU2243
在做本题之前推荐做POJ2778
POJ2778求的是用给定字符集构造出的长度为n的字符串中没出现过给定字符串的字符串有多少个
本题统计的是出现过的,那么我们只需要算出一共可能的种数,再算出长度为1-n可能的出现过给定字符串的字符串个数,相减就是答案。
首先,我们先计算一共可能的种数,
f[i]=261+262+.....26i f [ i ] = 26 1 + 26 2 + . . . . .26 i
所以 f[i]=26f[i1]+26 f [ i ] = 26 ∗ f [ i − 1 ] + 26
所以我们可以用矩阵快速幂求出一共可能的种数sum,注意这里要用unsigned long long
之后我们利用上题的做法求出转移矩阵,设转移矩阵为 T T ,那么 Tn T n 的第一行的和即为长度为n的串的转移方案而如果我们想求出所有长度的方案数之和,我们就需要在转移矩阵上加一维,用来存储上一个状态第一行之和,这样最后 Tn T n 的第一行之和就是长度为1-n所有方案之和sum2(慢慢理解,想一下矩阵快速幂
最后用sum-sum2即是最终结果,注意所有变量都应该用unsigned long long。而且这里不能直接输出sum-sum2会出现负数,应该用sum-=sum2才能实现自动溢出。
HDU2243代码

//由于本题存在两个大小不同的矩阵,所以开了两个struct和两个分别矩阵快速幂函数
//但是如果用类去构造应该能清晰一些。
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define  dbg(x) cout<<#x<<" "<
typedef unsigned long long ll;
const int maxn = 2e6+5;
map<char,int> mp;
struct ACTrie
{
    int tree[maxn][27],fail[maxn];
    int end_[maxn];
    int root,num,cnt;
    int newnode()
    {
        for(int i=0;i<26;i++)
            tree[cnt][i]=-1;
        end_[cnt]=0;
        return cnt++;
    }
    void init()
    {
        cnt=0;
        num=0;
        root=newnode();
    }
    void insert_(char str[])
    {
        int pos=root;
        int len=strlen(str);
        for(int i=0;iint id=str[i]-'a';
            if(tree[pos][id]==-1) tree[pos][id]=newnode();
            pos=tree[pos][id];
        }
        end_[pos]=1;
    }
    void build()
    {
        queue<int> que;
        fail[root]=root;
        for(int i=0;i<26;i++)
        {
            if(tree[root][i]==-1) tree[root][i]=root;
            else
            {
                fail[tree[root][i]]=root;
                que.push(tree[root][i]);
            }
        }
        while(!que.empty())
        {
            int now=que.front();
            que.pop();
            for(int i=0;i<26;i++)
            {
                if(tree[now][i]==-1) tree[now][i]=tree[fail[now]][i];
                else
                {
                    fail[tree[now][i]]=tree[fail[now]][i];
                    que.push(tree[now][i]);
                }
                end_[tree[now][i]]|=end_[tree[fail[now]][i]];
            }
        }
    }
};
ACTrie ac;
struct mat
{
    ll jz[110][110];
};
mat make_mat()
{
    mat res;
    memset(res.jz,0,sizeof(res.jz));
    for(int i=0;iif(ac.end_[i]) continue;
        for(int j=0;j<26;j++)
        {
            if(ac.end_[ac.tree[i][j]]) continue;
            ++res.jz[i][ac.tree[i][j]];
        }
    }
    for(int i=0;i<=ac.cnt;i++)//在最后一列加上1,实现当前行和的转移
    {
        res.jz[i][ac.cnt]=1;
    }
    return res;
}
mat mat_mul(mat x,mat y)
{
    mat res;
    memset(res.jz,0,sizeof(res.jz));
    for(int i=0;i<=ac.cnt;i++)
        for(int j=0;j<=ac.cnt;j++)
            for(int k=0;k<=ac.cnt;k++)
                res.jz[i][j]=res.jz[i][j]+x.jz[i][k]*y.jz[k][j];
        return res;
}
ll power_mod (ll b)//.res是系数矩阵,ans是变换矩阵左->ans,右->res.
{
    mat ans,res;
    res=make_mat();
    memset(ans.jz,0,sizeof(ans.jz));
    for(int i=0;i<=ac.cnt;i++)
        ans.jz[i][i]=1;
    while(b>0)
    {
        if(b&1)  ans=mat_mul(res,ans);//所以应该系数矩阵在前ans,res);
        b=b>>1;
        res=mat_mul(res,res);
    }
    ll tmp=0;
    for(int i=0;i<=ac.cnt;i++)
        tmp=tmp+ans.jz[0][i];
    return tmp;//返回指定位置元素
}
char str[11];
struct mat2
{
    ll jz[2][2];
};
mat2 mat_mul2(mat2 x,mat2 y)
{
    mat2 res;
    memset(res.jz,0,sizeof(res.jz));
    for(int i=0;i<2;i++)
        for(int j=0;j<2;j++)
            for(int k=0;k<2;k++)
                res.jz[i][j]=res.jz[i][j]+x.jz[i][k]*y.jz[k][j];
        return res;
}
ll power_mod2(ll b)//.res是系数矩阵,ans是变换矩阵左->ans,右->res.
{
    mat2 ans,res;
    res.jz[0][0]=26;
    res.jz[0][1]=1;
    res.jz[1][0]=0;
    res.jz[1][1]=1;//根据要求设定系数单位矩阵,
    ans.jz[0][0]=0;
    ans.jz[1][0]=26;//初始化ans矩阵,只需要初始化每行的第一列
    while(b>0)
    {
        if(b&1)  ans=mat_mul2(res,ans);//所以应该系数矩阵在前ans,res);
        b=b>>1;
        res=mat_mul2(res,res);
        //cout<
    }
    return ans.jz[0][0];//返回指定位置元素
}
int main()
{
    int n;
    ll m;
    while(scanf("%d%I64d",&n,&m)!=EOF)
    {
        ac.init();
        while(n--)
        {
            scanf("%s",str);
            ac.insert_(str);
        }
        ac.build();
        ll tmp=power_mod2(m)+1;//加上长度为0的串
        ll res=power_mod(m);
        tmp-=res;//注意是-=不是直接输出
        cout<return 0;
}

AC自动机第七题

UVA11019
本体题意就是给你AB两个字符矩阵,问你B矩阵在A矩阵中的出现次数
我们用B矩阵的每一行插入Trie树,用A的每一行去跑AC自动机,得到A的每个位置可以匹配B的哪些行,存到一个三维矩阵con[i][j][k]里,con[i][j][k]表示A矩阵第i行第j列的点可匹配B矩阵中的第k行,之后枚举A矩阵起点,然后向下扫x行,检验是否都可以匹配,都可以匹配就更新答案。
注意B中可能存在某些相同行,所以用一个vector进行存储

#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 5e5+5;
int strl[1005];
int n,m,x,y;
bool con[1005][1005][105];
struct ACTrie
{
    int tree[maxn][26],fail[maxn],end_[maxn];
    int root,cnt;
    vector<int> v[maxn];
    int newnode()
    {
        for(int i=0;i<26;i++)
            tree[cnt][i]=-1;
        end_[cnt++]=0;
        return cnt-1;
    }
    void init()
    {
        cnt=0;
        root=newnode();
    }
    void insert_(char str[],int xpos)
    {
        int len= strlen(str);
        int pos=root;
        for(int i=0;iint id=str[i]-'a';
            if(tree[pos][id]==-1)
                tree[pos][id]=newnode();
            pos=tree[pos][id];
        }
        if(end_[pos]==0)
        {
            v[pos].clear();
            end_[pos]=1;
        }
        v[pos].push_back(xpos);//可能B中某些行相同,所以存到一起
    }
    void build()
    {
        queue<int> que;
        fail[root]=root;
        for(int i=0;i<26;i++)
        {
            if(tree[root][i]==-1) tree[root][i]=root;
            else
            {
                fail[tree[root][i]]=root;
                que.push(tree[root][i]);
            }
        }
        while(!que.empty())
        {
            int now=que.front();
            que.pop();
            for(int i=0;i<26;i++)
            {
                if(tree[now][i]==-1)
                    tree[now][i]=tree[fail[now]][i];
                else
                {
                    fail[tree[now][i]]=tree[fail[now]][i];
                    que.push(tree[now][i]);
                }
            }
        }
    }
    void query(char str[],int xpos)
    {
        int len=strlen(str);
        int now=root;
        for(int i=0;i'a'];//在Trie上不断向后跑
            int temp=now;
            while(temp!=root)
            {
                if(end_[temp])
                {
                    for(int j=0;jint pp=v[temp][j];
                        con[xpos][i-y+1][pp]=true;//列的起始位置是i-y+1
                    }
                }
                temp=fail[temp];
            }
        }
        return ;
    }
};
char str[maxn*2];
char str1[1005][1005];
ACTrie ac;
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        for(int i=0;iscanf("%s",str1[i]);
        ac.init();
        scanf("%d%d",&x,&y);
        for(int i=0;iscanf("%s",str);
            ac.insert_(str,i);
        }
        ac.build();
        for(int i=0;ifor(int j=0;jfor(int k=0;kfalse;
                }
            }
        }
        for(int i=0;iint ans=0;
        for(int i=0;i<=n-x;i++)//注意边界
        {
            for(int j=0;j<=m-y;j++)//注意边界
            {
                int flag=0;
                for(int k=i,l=0;kif(!con[k][j][l])
                    {
                        flag=1;
                        break;
                    }
                }
                if(flag==0) ans++;//全都匹配
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

未完待续…

你可能感兴趣的:(AC自动机)