AC自动机的学习
花了一天把AC最基础的东西弄明白了,想弄清楚AC自动机就得学会KMP和字典树,因为这个算法是这个两个算法的基础上建立的。我们得明白字典树如何建立的和得深度理解KMP中next数组得含义。AC自动机中的fail指针和KMP中的next数组有异曲同工之处。fail指针的作用:在失配时能够跳转到合适的位置继续匹配。
AC自动机自己也是学习了基础,博客肯定时写不出来的。。。。。
参考博客:
AC自动机:http://www.cppblog.com/menjitianya/archive/2014/07/10/207604.html
fail指针的理解:https://blog.csdn.net/u013371163/article/details/60469145(我感觉很形象,很容易理解)
AC自动机的模板题 :HDU2222
附上蒟蒻的代码(有注释,可能有些地方理解的不对Orz)
///#include<bits/stdc++.h>
///#include<unordered_map>
///#include<unordered_set>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<bitset>
#include<set>
#include<stack>
#include<map>
#include<list>
#include<new>
#include<vector>
#define MT(a,b) memset(a,b,sizeof(a));
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double pai=acos(-1.0);
const double E=2.718281828459;
const int MAXN=1e5;
const ll mod=1e9+7;
const int INF=0x3f3f3f3f;
struct node
{
node *child[26]; ///儿子
node *fail; ///fail指针
int cnt; ///计数
};
node *root; ///根节点
char word[55]; ///单词
char text[1000005]; ///文本
void insert_word(char *s) ///建字典树
{
node *now=root;
int i=0,x;
while(s[i])
{
x=s[i]-'a';
if(now->child[x]==NULL) ///如果没有儿子
{
node *temp=new node(); ///申请空间
temp->cnt=0; ///初始化
temp->fail=NULL;
memset(temp->child,NULL,sizeof(temp->child));
now->child[x]=temp;
}
now=now->child[x];
i++;
}
now->cnt++; ///叶子节点计数
}
void build_fail() ///用父亲去更新儿子的fail
{
node *now,*temp;
queue<node *>q;
q.push(root); ///root先入队
while(!q.empty())
{
now=q.front();
q.pop();
for(int i=0;i<=25;i++)
{
if(now->child[i]!=NULL)
{
if(now==root) ///如果当前为根,他的儿子的fail都指向root
now->child[i]->fail=root;
else
{
temp=now->fail; ///定义一个变量用来转移当前的寻找状态
while(temp!=NULL) ///如果找到root的fail就寻找结束了
{
if(temp->child[i]!=NULL) ///如果找到了满足条件的fail
{
now->child[i]->fail=temp->child[i]; ///更新儿子的fail
break; ///跳出寻找
}
temp=temp->fail; ///继续寻找
}
if(temp==NULL) ///如果找到了root的fail
now->child[i]->fail=root; ///儿子的fail指向root
}
q.push(now->child[i]); ///儿子入队
}
}
}
}
int ac_auto(char *s)
{
node *now=root,*temp;
int i=0,x,ans=0;
while(s[i])
{
x=s[i]-'a';
///如果找不到这个字母,利用fail指针一直找
///如果找到这个字母就结束
///如果当前为root节点,就不需要寻找
while(now!=root&&now->child[x]==NULL)
now=now->fail;
///这一步针对是对root的判断
now=now->child[x];
if(now==NULL) ///如果root没有这个儿子,则回到root
now=root;
temp=now; ///定义临时变量去寻找答案
while(temp!=root&&temp->cnt>=0) ///如果当前不是root,并且这个地方没有来过
{
ans+=temp->cnt; ///cnt==0,不影响答案
temp->cnt=-1; ///标记这个地方来过
temp=temp->fail; ///继续寻找
}
i++;
}
return ans;
}
void init() ///初始化
{
root->fail=NULL; ///根的fail一定是空
root->cnt=0;
memset(root->child,NULL,sizeof(root->child)); ///清空儿子
}
int main()
{
int t,n;
root=new node(); ///根节点申请空间
scanf("%d",&t);
while(t--)
{
init(); ///初始化
scanf("%d",&n);
while(n--)
{
scanf("%s",word);
insert_word(word); ///插入单词
}
build_fail(); ///构建失败指针
scanf("%s",text);
printf("%d\n",ac_auto(text)); ///寻找
}
return 0;
}