zoukankan      html  css  js  c++  java
  • 【字符串处理】AC自动机知识点&代码

    代码:

    #include<iostream>
    #include<vector>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<cstdlib>
    #include<cmath>
    #include<algorithm>
    #include<set>
    #include<cstring>
    using namespace std;
    typedef long long ll;
    const ll INF=99999999;
    const int MAXN=1000024;
    int trie[MAXN][27];     //i到j的编号
    int val[MAXN];          //代表这个节点有多少单词
    int fail[MAXN];          //fail指针,意为失配时去的位置
    int cnt;                    
    queue<int> q;
    void ins(string str){                                  //添加单词
        int last=0;                                          //代表着多次失配之后最后跳到的地方                              
        for(int i=0;i<(int)str.length();i++){          
            int v=str[i]-'a';           
            if(!trie[last][v]) trie[last][v]=++cnt;    //如果没有对应的这个节点的话,就加上(标记)
            last=trie[last][v];                            //将上次的编号记住
        }
        val[last]++;                                       //单词数+1
    }
    void build(){
        for(int i=0;i<26;i++)                          //遍历与根节点连接的点
            if(trie[0][i]){                                 //如果有这个字母的儿子
                fail[trie[0][i]]=0;                       //将fail指针指向根
                q.push(trie[0][i]);                      //加入搜索队列
                
            }
        while(!q.empty()){
            int u=q.front();
            q.pop();
            for(int i=0;i<26;i++){                    //枚举其每个儿子
                if(trie[u][i]){                             //如果有这个节点
                    fail[trie[u][i]]=trie[fail[u]][i];    //fail指针指向父节点(当前节点)的fail指针指向的节点的相同字母节点
                    q.push(trie[u][i]);                   //加入这个点
                }
                else{
                    trie[u][i]=trie[fail[u]][i];         //没有这个点的话将其等同于父节点(当前节点)的fail指针的相同字母节点
                }
            }
        }
    }
    int query(string str){
        int last=0,ans=0;
        for(int i=0;i<(int)str.length();i++){
            last=trie[last][str[i]-'a'];                       //获得当前字母,当前位置的编号
            for(int j=last;j&&~val[j];j=fail[j]) {        //只要没有结尾,就按照fail路线走
                ans+=val[j];                                  //加上以这个节点结尾的单词书亮
                val[j]=-1;                                      //已经拿走了,所以没有了
            }
        }
        return ans;
    }
    int main()
    {
        ios_base::sync_with_stdio(false);
        cin.tie(0);
        int n;
        cin>>n;
        string st;
        for(int i=0;i<n;i++){
            cin>>st;
            ins(st);                    //添加单词
        }
        build();                       //初始化
        cin>>st;
        cout<<query(st);         //输出
        
        return 0;
    }
    查看神奇代码

    0.容器

    大部分人是用struct或者class实现内部的函数,但是作为考场上最倩的仔(雾,我决定使用数组存。

    (很不理解为什么要用struct存,OI又不是写工程)

    int trie[MAXN][27];     //代表从前、后的编号
    int val[MAXN];          //代表这个节点有多少单词
    int fail[MAXN];          //fail指针,意为失配时去的位置
    int cnt;                    //当前编号
    

    MAXN是数据的范围,是一个常量

    1.插入

    插入相当于从根一直走到最后一个字母的位置,没有就插入并赋予它一个新编号

    void ins(string str){                                  //添加单词
        int last=0;                                          //代表着多次失配之后最后跳到的地方                              
        for(int i=0;i<(int)str.length();i++){          
            int v=str[i]-'a';           
            if(!trie[last][v]) trie[last][v]=++cnt;    //如果没有对应的这个节点的话,就加上(标记为新的节点,赋予其新编号)
            last=trie[last][v];                            //将这次的编号记住,以便下次使用
        }
        val[last]++;                                       //单词数+1
    }
    

     

    2.Build 

    Build的是Fail指针:

    先把所有与根节点连接的点的Fail指针指向根,然后将它们加入队列

    然后BFS整棵树:

    取出队首,然后将其儿子的Fail指针指向父亲的fail指针指向的相同字母的节点

    赋予队首不存在的子节点父亲节点的Fail指针指向的相同字母的节点的编号

    void build(){
        for(int i=0;i<26;i++)                          //遍历与根节点连接的点
            if(trie[0][i]){                                 //如果有这个字母的儿子
                fail[trie[0][i]]=0;                       //将fail指针指向根
                q.push(trie[0][i]);                      //加入搜索队列
                
            }
        while(!q.empty()){
            int u=q.front();
            q.pop();
            for(int i=0;i<26;i++){                    //枚举其每个儿子
                if(trie[u][i]){                             //如果有这个节点
                    fail[trie[u][i]]=trie[fail[u]][i];    //fail指针指向父节点(当前节点)的fail指针指向的节点的相同字母节点
                    q.push(trie[u][i]);                   //加入这个点
                }
                else{
                    trie[u][i]=trie[fail[u]][i];         //没有这个点的话将其等同于父节点(当前节点)的fail指针的相同字母节点
                }
            }
        }
    }
    int query(string str){
        int last=0,ans=0;
        for(int i=0;i<(int)str.length();i++){
            last=trie[last][str[i]-'a'];                       //获得当前字母,当前位置的编号
            for(int j=last;j&&~val[j];j=fail[j]) {        //只要没有结尾,就按照fail路线走
                ans+=val[j];                                  //加上以这个节点结尾的单词书亮
                val[j]=-1;                                      //已经拿走了,所以没有了
            }
        }
        return ans;
    }
    

      

    3.顺序

    输入单词

    ins

    build

    输出query

    int n;
        cin>>n;
        string st;
        for(int i=0;i<n;i++){
            cin>>st;
            ins(st);                    //添加单词
        }
        build();                       //初始化
        cin>>st;
        cout<<query(st);         //输出
    

      

    本模版可以在洛谷P3808 https://www.luogu.org/problemnew/show/P3808提交,已通过

  • 相关阅读:
    Eclipse / android studio 添加第三方jar包 步骤
    Android checkbox 自定义点击效果
    Android 程序打包和安装过程
    Android 基础
    (转)Genymotion安装virtual device的“unable to create virtual device, Server returned Http status code 0”的解决方法
    (转)eclipse 导入Android 项目 步骤
    微信开放平台注册 步骤
    Android Studio 初级安装
    数组
    作用域问题代码
  • 原文地址:https://www.cnblogs.com/dudujerry/p/9914912.html
Copyright © 2011-2022 走看看