ac 自动机

本文详细介绍AC自动机的构造过程及其与KMP算法的关系,包括字典树的建立、失配边的计算及如何与目标字符串进行匹配。通过具体实例hdu3065,展示了AC自动机在处理多个模式串时的高效性和灵活性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

/*以hdu 3065为例
ac自动机=kmp+trie


与kmp的思想是一样的,不过实现起来就有相当多的细节问题了。
1.建立字典树是必须的,ch[i][j],i是节点编号,j是儿子。每一个节点都隐含有MAX_SIZE个儿子的指针,0表示该儿子不存在,其他值表示她的位置
2.算出失配边,对于ch[i][j]!=0的处理方法与kmp中是相同的。
但对于ch[i][j]=0的处理有两种,
①不处理,直接跳过,continue
②把ch[i][j],赋值为 ch[Fail[i]][j],这样做的原因是简化代码,提高程序效率。
分析:
在kmp中,有这么一句话while(j>=0&&T[i]!=S[j+1])j=Fail[j];即: 当扫到目标串的i位时,不匹配则沿着失配边一直走下去。
同样 ac自动机中也是这样:当扫到目标串的i位时,不匹配则沿着失配边一直走下去。
②的处理先一步走完了相连的第一个失配边。失配边是按节点的深度递推下来的,
而失配边边必然连向了一个深度较小的节点,较小的点的失配边已经处理,
这样ch[i][j]直接变成了第一个能够与T正在处理的那一位匹配的节点的编号或0(根节点)。
当开始与T匹配时,就不再需要沿失配边走的语句了,直接j=ch[j][i],i是T的那一位的值(说的模糊,看代码就知道了)
3.开始与T匹配了,处理与kmp几乎完全相同,就是2.②的变化。。对于不同的题处理也就不同了,当有多个目标串T对Ac_Trie操作时,一定要保证Ac_Trie不变!!
*/


#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
#include<string>
#include<algorithm>
using namespace std;


int const MAX_NODE=1010*55;
int const MAX_SIZE=128;
char mp[1010][55];
int ans[1010];
struct Ac_Trie
{
    int ch[MAX_NODE][MAX_SIZE],Fail[MAX_NODE],value[MAX_NODE];
/*
ch[][]用来建字典树,Fail[]表示失配边,value[]存储的是字典树中该节点存储的信息
value[i]=-1,表示从root到编号为i的节点所形成的字符串不存在
ch[i][j],表示编号为i的节点的第j个儿子节点,ch[i][j]=0表示此路径不存在,否则她的值为节点i沿边j走下去的下一个节点编号
其中ch[i][j]=-1,0,可赋予不同的意义,视题意而定
*/


    int root,tot;
    int newnode()
    {
        memset(ch[tot],0,sizeof(ch[tot]));
        value[tot++]=-1;
        return tot-1;
    }
    void init()
    {
        tot=0;
        root=newnode();
    }
    void insert(char *s,int key)//构造trie树
    {
        int len=strlen(s);
        int u=root;
        for(int i=0;i<len;i++)
        {
            int v=s[i];
            if(!ch[u][v])
                ch[u][v]=newnode();
            u=ch[u][v];
        }
        value[u]=key;
    }


    void build()//构造自动机
    {
        queue<int>Q;
        Fail[root]=root;
        for(int i=0;i<MAX_SIZE;i++)
            if(ch[root][i])
            {
                Fail[ch[root][i]]=root;
                Q.push(ch[root][i]);
            }
        while(!Q.empty())
        {
            int now=Q.front();Q.pop();
            for(int i=0;i<MAX_SIZE;i++)
            {
                if(ch[now][i])
                {
                    Fail[ch[now][i]]=ch[Fail[now]][i];
                    Q.push(ch[now][i]);
                }
                else ch[now][i]=ch[Fail[now]][i];
            }
        }
    }


    void Ac(char *T)//与目标串匹配
    {
        int len=strlen(T);
        int u=root;
        for(int i=0;i<len;i++)
        {
            int v=T[i];
            u=ch[u][v];
            int temp=u;
            while(temp)
            {
                if(value[temp]!=-1)
                    ans[value[temp]]++;
                temp=Fail[temp];
            }
        }
    }


};


char T[2000010];
Ac_Trie Ac;
int main()
{
    int n,m;
    while(scanf("%d",&n)!=EOF)
    {
        Ac.init();
        for(int i=1;i<=n;i++)
        {
            scanf("%s",mp[i]);
            Ac.insert(mp[i],i);
        }
        Ac.build();
        scanf("%s",T);
        memset(ans,0,sizeof(ans));
        Ac.Ac(T);
        for(int i=1;i<=n;i++)
        {
           // cout<<ans[i]<<endl;
            if(ans[i])
                printf("%s: %d\n",mp[i],ans[i]);
        }
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值