【字符串处理】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提交,已通过

原文地址:https://www.cnblogs.com/dudujerry/p/9914912.html