zoukankan      html  css  js  c++  java
  • 【字符串】BZOJ上面几个AC自动机求最为字串出现次数的题目

    (一下只供自己复习用,目的是对比这几个题,所以写得不详细。需要细节的可以参考其他博主)

    【BZOJ3172:单词】

    题目:

             某人读论文,一篇论文是由许多(N)单词组成。但他发现一个单词会在论文中出现很多次,现在想知道每个单词分别在论文中出现多少次。N<=200,总单词长度不超过10^6。

    思路:

            简单题,建立AC自动机,插入的时候每个位置都++,代表以当前位置为后缀的字符串的个数,用于fail转移时累加。然后build得到fail指针;最后从叶子向根累加。

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1000010;
    char c[maxn]; int ans[maxn],pos[210],N;
    struct Trie
    {
        int ch[maxn][26],cnt,times,fail[maxn],q[maxn],head,tail;
        Trie(){ cnt=times=head=tail=0; }
        int insert(){
            int Now=0,L=strlen(c+1);
            for(int i=1;i<=L;i++){
                if(!ch[Now][c[i]-'a']) ch[Now][c[i]-'a']=++cnt;
                Now=ch[Now][c[i]-'a'];
                ans[Now]++;
            }
            return Now;
        }
        void build()
        {
            for(int i=0;i<26;i++)
              if(ch[0][i]) q[++head]=ch[0][i];
            while(tail<head){
                int Now=q[++tail];
                for(int i=0;i<26;i++){
                    if(ch[Now][i]){
                        fail[ch[Now][i]]=ch[fail[Now]][i];
                        q[++head]=ch[Now][i];
                    }
                    else ch[Now][i]=ch[fail[Now]][i];
                }        
            }
            for(int i=tail;i>=1;i--) ans[fail[q[i]]]+=ans[q[i]];
        } 
    }T;
    int main()
    {
        scanf("%d",&N);
        for(int i=1;i<=N;i++){
            scanf("%s",c+1);
            pos[i]=T.insert();
        }
        T.build(); 
        for(int i=1;i<=N;i++) printf("%d
    ",ans[pos[i]]);
        return 0;
    }
    View Code

    【BZOJ2434阿狸的打字机】:

    题目:

              给定N个字符串。现在又Q个问题,每次问题给出(X,Y),求第X个字符串在第Y个字符串里出现的次数。 1<=N<=10e5;1<=M<=10e5;输入总长<=10e5

    思路:

             因为上一题是单次讯问,而且是整体求,所以一次拓扑倒序累加即可,但是此题是多次询问,而且是针对Trie树上代表的两个字符串X和Y之间的包含次数,不能整体法。其实问题是 “Y有多少个节点顺着fail可以走到X节点”; 正解: 1,先建立AC自动机;2,得到fail树;3,对fail树进行DFS得到DFS序;4,在Trie树上dfs求解。

           下面这一段转自huzecong (便于理解,自己加了一点东西) :那么我们可以得到一个离线算法:对fail树遍历一遍,得到一个DFS序(得到fail树每个节点的子树);再维护一个树状数组,对原Trie树进行dfs遍历,每访问一个节点,就修改树状数组,对树状数组中该节点的DFS序起点的位置加上1。每往回走一步,就减去1(模拟一下发现dfs到Y点时,在树状数组里的东西来自于root、Y1、Y2...Y,是一条链;即Y的每一个节点都考虑到是否可以沿fail指针走到X)。如果访问到了一个Y字串的末尾节点,枚举询问中每个Y串对应的X串,查询树状数组中X串末尾节点从DFS序中的起始位置到结束位置的和,并记录答案。这样,我们就得到了一个时间复杂度为O(N+MlogN)的优美的算法。因为N和M都不超过105,所以这个算法是可行的。
     
    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=100010;
    char c[maxn]; int pos[maxn],num,ans[maxn]; //记录输入字符串位置 
    vector<int>G[maxn]; //用于建fail树 
    int Laxt[maxn],Next[maxn],To[maxn],id[maxn],tot; //N个问题
    void add(int u,int v,int iid) //加问题 
    {
        Next[++tot]=Laxt[u]; 
        Laxt[u]=tot; 
        To[tot]=v;   
        id[tot]=iid;
    }     
    struct Trie
    {
        int cnt,ch[maxn][26],fa[maxn],fail[maxn],q[maxn],head,tail;//建立AC自动机,fail树。 
        int ind[maxn],outd[maxn],times; //对Fail树DFS序部分 
        int sum[maxn<<1]; //对Trie树dfs维护的树状数组部分
        Trie(){ cnt=times=head=tail=0; }
        void insert(){
            int Now=0,L=strlen(c+1);
            for(int i=1;i<=L;i++){
                if(c[i]=='B') Now=fa[Now];
                else if(c[i]=='P') pos[++num]=Now;
                else {
                    if(!ch[Now][c[i]-'a']){
                        ch[Now][c[i]-'a']=++cnt;
                        fa[cnt]=Now;
                    }
                    Now=ch[Now][c[i]-'a'];  
                }
            }
        }
        void build()
        {
            for(int i=0;i<26;i++)
            if(ch[0][i]){ 
                  q[++head]=ch[0][i];
                  G[0].push_back(ch[0][i]); 
            } 
            while(tail<head){
                int Now=q[++tail];
                for(int i=0;i<26;i++){
                    if(ch[Now][i]){
                        fail[ch[Now][i]]=ch[fail[Now]][i];
                        G[fail[ch[Now][i]]].push_back(ch[Now][i]);
                        q[++head]=ch[Now][i];
                    }
                    else ch[Now][i]=ch[fail[Now]][i]; 
                }        
            }
        }
        void DFS(int Now)
        {
            ind[Now]=++times;
            int L=G[Now].size();
            for(int i=0;i<L;i++) DFS(G[Now][i]);
            outd[Now]=++times;
        }
        void add(int x,int val){ while(x<=times) { sum[x]+=val; x+=(-x)&x;}}
        int query(int x){  int res=0; while(x){ res+=sum[x];  x-=(-x)&x;}  return res;}
        void dfs()
        {
            int Now=0,L=strlen(c+1);
            for(int i=1;i<=L;i++){
                if(c[i]=='B') add(ind[Now],-1) ,Now=fa[Now];
                else if(c[i]=='P'){ 
                    for(int j=Laxt[Now];j;j=Next[j]){ 
                        int v=To[j];
                         ans[id[j]]=query(outd[v])-query(ind[v]-1);
                    }
                }
                else Now=ch[Now][c[i]-'a'],add(ind[Now],1);
            }
        }
    }T;
    int main()
    {
        scanf("%s",c+1);
        T.insert(); T.build();
        int N,u,v; scanf("%d",&N);
        for(int i=1;i<=N;i++){
            scanf("%d%d",&u,&v);
            add(pos[v],pos[u],i);
        }
        T.DFS(0);  
        T.dfs(); //直接搜索是错的,需要从输入的字符串入手。因为ac自动机ch[]数组的关系是改变了的 
        for(int i=1;i<=N;i++) printf("%d
    ",ans[i]);
        return 0;
    }
    View Code
     
     
    【BZOJ3881】
      题意:
             Bob有个字符串集合T,一开始为空,现在有两种操作:1,Bob的集合新增加一个字符串str; 2,Alice给出字符串x,问集合T中多少字符串包含x。1e6级别。
     
     
  • 相关阅读:
    C++运算符优先级
    UNREFERENCED_PARAMETER
    Eclipse无法查看源码 提示source not found
    JAVAWEB dbutils执行sql命令并遍历结果集时不能查到内容的原因
    JAVAWEB和数据库 Mysql连接不上的原因及解决方案
    电子设备 Kindle如何删除书籍或漫画
    JAVA StringUtils需要导入的包
    JAVAWEB servlet和jsp的权限访问控制
    JAVAWEB servlet验证登录时进行完全的非空判断防止空值登录
    JAVAWEB 遍历mysql结果集时 字段为0、false、null的问题
  • 原文地址:https://www.cnblogs.com/hua-dong/p/9004685.html
Copyright © 2011-2022 走看看