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提交,已通过

  • 相关阅读:
    阶段3 3.SpringMVC·_03.SpringMVC常用注解_1 RequestParam注解
    阶段3 3.SpringMVC·_02.参数绑定及自定义类型转换_7 获取Servlet原生的API
    函数传参
    利用 操作符特性 代替if判断语句
    for(;;)和 while(1) 有什么区别吗?for()和while()的使用情景。
    一个简单的mfc单页界面文件读写程序(MFC 程序入口和执行流程)
    ARM异常---一个DataAbort的触发过程:
    C语言,单链表操作(增删改查)(version 0.1)
    Cpu实验
    H-JATG:NAND_FLASH的参数设置
  • 原文地址:https://www.cnblogs.com/dudujerry/p/9914912.html
Copyright © 2011-2022 走看看