AC自动机学习小记

前置技能

  • Tire
  • KMP算法

算法简介

AC自动机,Aho-Corasick automaton,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法。


分析:

其实AC自动机就是tire加上KMP。
一个简单的问题(hdu2222):给出多个模式串,在给出一个文本串,问模式串在文本串中出现了多少次。
最简单的做法就是将模式串加入的tire中,在用文本串逐位去匹配。
思考KMP的做法,借助失配指针利用已匹配的字符串,将时间复杂度降到 O(n) O ( n )
考虑将失配指针放到tire上
我们定义str(x)表示tire上节点x所表示的串,失配指针fail[x]所指向的节点y满足str(y)为str(x)的最长后缀。这里写图片描述
fail指针大概就长这个样子。
得到fail以后我们如何进行匹配?以图中的tire为例,对”heshis”进行匹配。
首先我们会沿着tire的边走到2点,匹配到一个模式串he,发现下一个点失配,那么我们沿着fail边跳到0。继续沿着边走,到4点失配,沿着fail边走到1点。向下走到7点匹配第二个模式串his。
这样我们就可以大概想到AC自动机的匹配方式:沿着tire的边走,失配就往fail边走。

code:
int query(char s[]){
    int x=0,len=strlen(s),tot=0;
    for (int i=0;i<len;i++) {
        x=a[x][s[i]-'a'];
        int y=x;
        while (y!=0) {
            tot+=sum[y];
            sum[y]=0;
            y=fail[y];
        }
    }
    return tot;
}

如何构造fail

构造AC自动机的fail边,需要按照bfs序来做。
why?fail边指向的是最长后缀,那么str(fail[x])应该要比str(x)短。这样就保证了比str(x)要短的串已经构造完了。
现在我们有一个点x,以及它的父亲y,已知fail[y],如何求fail[x]。
str(fail[y])+c(一个字符)如果等于str(x),那么fail[x]就是c所表示的y的儿子。
如果不相等,那么我们令y=fail[y],继续进行上面的操作。

code
void build() {
    int i,j,k;
    for (i=0;i<26;i++) {
        if (a[0][i]==-1) a[0][i]=0; 
        else fail[a[0][i]]=0,d.push(a[0][i]);
    }
    while (!d.empty()) {
        int x=d.front();
        for (i=0;i<26;i++) {
            if (a[x][i]==-1) a[x][i]=a[fail[x]][i];
            else fail[a[x][i]]=a[fail[x]][i],d.push(a[x][i]);
        }
        d.pop();
    }
}

时间复杂度

假设有 n n 个模式串,平均长度为l;文章长度为 m m
建立Trie树:O(nl)
建立fail指针: O(nl) O ( n l )
模式匹配: O(m(l)) O ( m ( l ) ) (之所以要乘以一个 l l <script type="math/tex" id="MathJax-Element-9">l</script>,是因为在统计的时候需要顺着链回溯到root结点)


hdu2222
一道AC自动机模板题

code

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N=1e6+10,M=1e6+10;
queue<int> d;
char s[N];
int n;
struct ac{
    int a[M*10][26],fail[M*50],sum[M*50],tot;
    int makenew() {
        memset(a[tot],-1,sizeof(a[tot]));
        fail[tot]=sum[tot]=0;
        return tot++;
    }
    void insert(char s[]){
        int x=0,i,j,k,len=strlen(s);
        for (i=0;i<len;i++) {
            if (a[x][s[i]-'a']==-1) a[x][s[i]-'a']=makenew();
            x=a[x][s[i]-'a'];
        }
        sum[x]++;
    }
    void build() {
        int i,j,k;
        for (i=0;i<26;i++) {
            if (a[0][i]==-1) a[0][i]=0; 
            else fail[a[0][i]]=0,d.push(a[0][i]);
        }
        while (!d.empty()) {
            int x=d.front();
            for (i=0;i<26;i++) {
                if (a[x][i]==-1) a[x][i]=a[fail[x]][i];
                else fail[a[x][i]]=a[fail[x]][i],d.push(a[x][i]);
            }
            d.pop();
        }
    }
    int query(char s[]){
        int x=0,len=strlen(s),tot=0;
        for (int i=0;i<len;i++) {
            x=a[x][s[i]-'a'];
            int y=x;
            while (y!=0) {
                tot+=sum[y];
                sum[y]=0;
                y=fail[y];
            }
        }
        return tot;
    }
    void init() {
        tot=0;
        makenew();
    }
}aca;
void work() {
    int i,j,k;
    scanf("%d",&n);
    scanf("\n");
    aca.init();
    for (i=1;i<=n;i++) {
        scanf("%s",s);
        aca.insert(s);
    }
    aca.build();
    scanf("%s",s);
    printf("%d\n",aca.query(s));
}
int main() {
    int t;scanf("%d",&t);
    while (t--) work();
}

例题:

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值