浅谈AC自动机

标题是浅谈,那么我也就不过多的深入钻研了

顶多算是说说定义,带大家看看代码

首先,AC自动机,很多人看了这个算法名字,就自然的很慌,就不敢学

其实静下心来,慢慢看,会发现其实比KMP还要容易理解(只不过是多模式串匹配,树形结构还很容易看出共同的前缀)

AC自动机其实是在Trie树上进行一个类似于KMP的匹配操作(来自某Dalao)

首先是要建一棵Trie字典树,具体如下:

void pre(){
    int c=1;
    for(int i=1;i<=len;i++){
        int f=a[i]-'a';
        if(!trie[c][f]) trie[c][f]=++tot;//判断在之前是否出现过
        c=trie[c][f];
    }
    vis[c]++;//根据题目描述的不同改变
    return ;
}

然后是建立nxt数组,听着很难,我们不过多解释,只来说一下表面的意思

我们现在有两个串A,B

A:bb

B:ba

建立Trie树

如下:

 

然后我们先不着急,来想一想nxt数组具体代表的是什么,很多博客都只是讲了如何操作,我先省去那一部分,来说说为什么我们要记录一个nxt

nxt[i]所代表的,适当你在节点i失配时,从哪里开始匹配

相当于是在AC自动机上找到一个位置的前缀等于当前匹配到的串的后缀

然后来看样例

2因为没有与之相同的,所以直接指向1

3是a,也没有相同的,也指向1

最后是4,我们发现ab得前缀b是与之对应的,所以4指向2

完成后如下:

然后就是匹配了,和Trie树差不多,只不过是在失配的时候从nxt所指向的字符开始匹配

上一道板子题

试题描述
给定 n 个长度不超过 50 的由小写英文字母组成的单词准备查询,以及一篇长为 m 的文章,问:文中出现了多少个待查询的单词。多组数据。
输入
第一行一个整数 T,表示数据组数;
对于每组数据,第一行一个整数 n,接下去 n 行表示 n 个单词,最后一行输入一个字符串,表示文章。
输出
对于每组数据,输出一个数,表示文中出现了多少个待查询的单词。
输入示例
1
5
she
he
say
shr
her
yasherhs
输出示例
3

下面给出代码:

 

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
using namespace std;
inline long long rd(){
    long long x=0,f=1;
    char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
    for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';
    return x*f;
}
inline void write(long long x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
    return ;
}
int T;
char a[10006];
char s[1000006];
int trie[10006][36];
int vis[10006];
int len=0;
int tot=1;
void pre(){
    int c=1;
    for(int i=1;i<=len;i++){
        int f=a[i]-'a';
        if(!trie[c][f]) trie[c][f]=++tot;
        c=trie[c][f];
    }
    vis[c]++;
    return ;
}
int nxt[100006];
int ans=0;
int q[100006];
int l=0,r=0;
void get_next(){
    l=0,r=0;
    for(int i=0;i<=25;i++) trie[0][i]=1;
    nxt[1]=0;
    q[++r]=1;
    while(l<r){
        int h=q[++l];
        for(int i=0;i<=25;i++){
            if(!trie[h][i]) trie[h][i]=trie[nxt[h]][i];//便于查找,我们建立一个虚点, 
            else{
                nxt[trie[h][i]]=trie[nxt[h]][i];//一个点的nxt是它的父亲的nxt的和它一样的儿子,至于为什么,画个图就懂了 
                q[++r]=trie[h][i];
            }
        }
    }
    return ;
}
void solve(){
    int c=1;
    for(int i=1;i<=len;i++){
        int f=s[i]-'a';
        int k=trie[c][f];
        while(k>1){
            if(vis[k]){
                ans+=vis[k]; 
                vis[k]=0;
            }
            k=nxt[k];
        }
        c=trie[c][f];
    }
    return ;
}
int main(){
    T=rd();
    while(T--){
        memset(vis,0,sizeof(vis));
        memset(trie,0,sizeof(trie));
        tot=1;
        ans=0;
        int n;
        n=rd();
        for(register int i=1;i<=n;i++){
            scanf("%s",a+1);
            len=strlen(a+1);
            pre();//建立Trie树 
        }
        scanf("%s",s+1);
        len=strlen(s+1);
        get_next();
        solve();
        write(ans);
        puts("");
    }
    return 0;
}

 

posted @ 2018-10-06 16:19  Bruce--Wang  阅读(275)  评论(0编辑  收藏  举报