字符串总结之ac自动机

ac不是accepted的意思,而是作者的名字。

首先要学会ac自动机,就要先学会kmp算法和trie树(字典树)。

然而我为了写一篇ac自动机的博客,先写了trie的模板,还写了kmp的模板,所以在这里我就不介绍kmp和trie树了,就用简洁的文字讲述ac自动机的模板。

首先,我们先构造一棵trie树,对于每一个节点我们多设一个*fail指针,指向的位置----可以类比kmp的next数组。

若真的想要全面了解ac自动机的原理,可以搜寻一下其他的博客。

首先是一个结构体,链状的,跟trie树一样:(多一个fail)

struct node
{
    node *fail;
    node *son[26];
    int sz;
    node()
    {
        for(int i=0; i<=25; i++)
        {
            son[i]=NULL;
        }
        fail=NULL;
        sz=0;
    }
};

接着,也有一个类似于trie的Insert函数:

void my_insert(string ss)
{
    node *p=&st;
    for(int i=0; i<ss.size(); i++)
    {
        if(p->son[ss[i]-'a']==NULL)
        {
            p->son[ss[i]-'a']=new node;
        }
        p=p->son[ss[i]-'a'];
    }
    p->sz++;
}

然后是构造fail的函数,就像kmp先gouz构造next函数一样。利用广搜,逐步对每一个节点的failjinxin进行赋值:

void my_find_fail()
{
    queue<node*> q;
    node *p;
    node *temp;
    q.push(&st);
    while(!q.empty())
    {
        temp=q.front();
        q.pop();
        for(int i=0; i<=25; i++)
        {
            if(temp->son[i])
            {
                if(temp==&st)
                {
                    temp->son[i]->fail=&st;
                }
                else
                {
                    p=temp->fail;
                    while(p)
                    {
                        if(p->son[i])
                        {
                            temp->son[i]->fail=p->son[i];///不断的找fail,终止条件有两条
                            break;                       ///一。p==NULL;二。p->son[i]不为空,可以利用kmp的next来思考,但还是有点区别,具体为什么,可以看别人的博客。
                        }
                        p=p->fail;
                    }
                    if(p==NULL)
                        temp->son[i]->fail=&st;
                }
                q.push(temp->son[i]);
            }

        }
    }
}

 

接下来是查找函数了:

就是先把要判断的单词插入到树里,然后再以需要判断的字符串来判断,就是把需要被判断的字符串,扫一遍所建的trie树:

void my_ac_automation(string ss)
{
    node *p= &st;
    int l=ss.size();
    for(int i=0; i<l; i++)
    {
        while(!p->son[ss[i]-'a']&&p!=&st)
            p=p->fail;
        p=p->son[ss[i]-'a'];
        if(!p)
            p=&st;
        node *temp=p;
        while(temp!=&st)
        {
            if(temp->sz>=0)
            {
                counts+=temp->sz;
                temp->sz=-1;
            }
            else
                break;
            temp=temp->fail;
        }
    }
}

这就是ac自动机模板的所有函数

下面给一道简单的模板提练一下,主要是用来检查模板是否正确。(洛谷3808)

 

P3808 【模板】AC自动机(简单版)

题目背景

这是一道简单的AC自动机模板题。

用于检测正确性以及算法常数。

为了防止卡OJ,在保证正确的基础上只有两组数据,请不要恶意提交。

管理员提示:本题数据内有重复的单词,且重复单词应该计算多次,请各位注意

题目描述

给定n个模式串和1个文本串,求有多少个模式串在文本串里出现过。

输入输出格式

输入格式:

 

第一行一个n,表示模式串个数;

下面n行每行一个模式串;

下面一行一个文本串。

 

输出格式:

 

一个数表示答案

 

输入输出样例

输入样例#1: 复制

2
a
aa
aa

输出样例#1: 复制

2

说明

subtask1[50pts]:∑length(模式串)<=10^6,length(文本串)<=10^6,n=1;

subtask2[50pts]:∑length(模式串)<=10^6,length(文本串)<=10^6;

 

下面是我自己用指针写的模板程序:

///ac自动机
#include<bits/stdc++.h>
using namespace std;
struct node
{
    node *fail;
    node *son[26];
    int sz;
    node()
    {
        for(int i=0; i<=25; i++)
        {
            son[i]=NULL;
        }
        fail=NULL;
        sz=0;
    }
};
node st;
int counts;
void my_insert(string ss)
{
    node *p=&st;
    for(int i=0; i<ss.size(); i++)
    {
        if(p->son[ss[i]-'a']==NULL)
        {
            p->son[ss[i]-'a']=new node;
        }
        p=p->son[ss[i]-'a'];
    }
    p->sz++;
}
void my_find_fail()
{
    queue<node*> q;
    node *p;
    node *temp;
    q.push(&st);
    while(!q.empty())
    {
        temp=q.front();
        q.pop();
        for(int i=0; i<=25; i++)
        {
            if(temp->son[i])
            {
                if(temp==&st)
                {
                    temp->son[i]->fail=&st;
                }
                else
                {
                    p=temp->fail;
                    while(p)
                    {
                        if(p->son[i])
                        {
                            temp->son[i]->fail=p->son[i];///不断的找fail,终止条件有两条
                            break;                       ///一。p==NULL;二。p->son[i]不为空,可以利用kmp的next来思考,但还是有点区别,具体为什么,可以看别人的博客。
                        }
                        p=p->fail;
                    }
                    if(p==NULL)
                        temp->son[i]->fail=&st;
                }
                q.push(temp->son[i]);
            }

        }
    }
}
void my_ac_automation(string ss)
{
    node *p= &st;
    int l=ss.size();
    for(int i=0; i<l; i++)
    {
        while(!p->son[ss[i]-'a']&&p!=&st)
            p=p->fail;
        p=p->son[ss[i]-'a'];
        if(!p)
            p=&st;
        node *temp=p;
        while(temp!=&st)
        {
            if(temp->sz>=0)
            {
                counts+=temp->sz;
                temp->sz=-1;
            }
            else
                break;
            temp=temp->fail;
        }
    }
}
int main()
{
    int n;
    cin>>n;
        string s;
        for(int i=1;i<=n;i++)
        {
            cin>>s;
        //my_insert(s);
        my_insert(s);
        }

    my_find_fail();
    string sss;
    cin>>sss;
    my_ac_automation(sss);
    cout<<counts;
    return 0;
}

还有一点需要我们注意:

我前几次用指针来写广搜,队列是这样定义的:queue<node>q;

这就有问题了:我每次让队首出队之前,总是有temp=&q.front();

其实取出来的bing并不是原来的指针,电脑又生成了一个新的指针,这样我们接下来的操作就会有莫名的问题:

re...

这个问题真的究极可拍,希望用指针的朋友注意这一点。(用数组的巨佬可以不看这)

如果有问题,huan欢迎交流。

在这里推荐一篇博客:https://blog.csdn.net/creatorx/article/details/71100840

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值