AC自动机讲解:http://blog.csdn.net/creatorx/article/details/71100840 ------------>(讲的很简单也很清楚的博文地址)
AC自动机理解的部分还是很好理解的,字典树加上KMP的匹配失败转移的思想。
只是AC自动机的代码部分比较难理解。
以下是自己对AC自动机模板以及理解部分。
模板:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int maxn=1e6+50;
struct Tries{
int nxt[maxn][26],fail[maxn],end[maxn];
/*
nxt[i][j] 数组储存第i个节点的'a'+j字符储存的节点;
fail[i]表示第i个fail储存的匹配失败应该转移的节点;
end[i]表示从根节点到第i个节点的这个字符串出现的次数;
*/
int root,L;
/*
L 为节点的编号;
root 为根节点;
*/
int newnode(){
for(int i = 0; i < 26 ; i++ )
nxt[L][i]=-1;
end[L++]=0;
return L-1;
}
/*
创立新节点;
*/
void init(){
L=0;
root = newnode();
}
/*
初始化;
*/
void insert(char buf[]){
int len = strlen(buf);
int now = root;
for(int i = 0; i < len ; i++ )
{
if(nxt[now][buf[i]-'a'] == -1)
nxt[now][buf[i]-'a'] =newnode();
now = nxt[now][buf[i]-'a'];
}
end[now]++ ;
}
/*
插入新的字符串;
如果该字符没有在该位置出现过,则建立新的节点;
如果出现过,则继续向后更新;
每次对该字符串的数目进行更新;
*/
void build(){
queue<int>Q;
fail[root] = root;
for(int i = 0; i < 26; i++ )
if(nxt[root][i]==-1)
nxt[root][i] = root;
else
{
fail[nxt[root][i]] = root;/* *** */
Q.push(nxt[root][i]);
}
while(!Q.empty())
{
int now = Q.front();
Q.pop();
for(int i = 0;i < 26; i++ )
if(nxt[now][i]==-1)//如果该节点的子节点都不存在,那么该子节点节点变为该节点的转移节点的子节点
nxt[now][i] = nxt[fail[now]][i]; /* *** */
else//如果存在,那么该子节点的转移节点为该节点的转移节点的子节点,该子节点入队列
{
fail[nxt[now][i]] = nxt[fail[now]][i];
Q.push(nxt[now][i]);
}
}
}
/*
创建匹配失败时的转移数组;
根节点与第一层部分匹配失败后转移至根节点部分;
其他部分则转移至该字符出现过的节点位置;
*/
int query(char buf[])
{
int len = strlen(buf);
int now = root;
int res = 0;
for(int i = 0;i < len ; i++ )//如果子节点存在,则顺着该链一直匹配下去;如果不存在就一直转移,直到存在或者转移到根节点(now会更新到根节点)
{
now = nxt[now][buf[i]-'a'];
int tmp = now;
while( tmp != root)
{
res += end[tmp];
end[tmp] = 0;
tmp = fail[tmp];
}
}
return res;
}
/*
输入需要拿来匹配的串;
一次次的对当前串的各位进行匹配,如果包含当前字符的串在字典树中有出现,则答案累计加上该出现次数,且跳转转移节点进行计数;
因为end记录的是出现的次数,所以在需要匹配的串中如果有该字符,那么进行清0,则能保证字典树中的串在母串中出现一次;
*/
};
Tries AC;
hdu2222:
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <queue>
using namespace std;
const int maxn=1e6+50;
struct Tries{
int nxt[maxn][26],fail[maxn],end[maxn];
/*
nxt[i][j] 数组储存第i个节点的'a'+j字符储存的节点;
fail[i]表示第i个fail储存的匹配失败应该转移的节点;
end[i]表示从根节点到第i个节点的这个字符串出现的次数;
*/
int root,L;
/*
L 为节点的编号;
root 为根节点;
*/
int newnode(){
for(int i = 0; i < 26 ; i++ )
nxt[L][i]=-1;
end[L++]=0;
return L-1;
}
/*
创立新节点;
*/
void init(){
L=0;
root = newnode();
}
/*
初始化;
*/
void insert(char buf[]){
int len = strlen(buf);
int now = root;
for(int i = 0; i < len ; i++ )
{
if(nxt[now][buf[i]-'a'] == -1)
nxt[now][buf[i]-'a'] =newnode();
now = nxt[now][buf[i]-'a'];
}
end[now]++ ;
}
/*
插入新的字符串;
如果该字符没有在该位置出现过,则建立新的节点;
如果出现过,则继续向后更新;
每次对该字符串的数目进行更新;
*/
void build(){
queue<int>Q;
fail[root] = root;
for(int i = 0; i < 26; i++ )
if(nxt[root][i]==-1)
nxt[root][i] = root;
else
{
fail[nxt[root][i]] = root;/* *** */
Q.push(nxt[root][i]);
}
while(!Q.empty())
{
int now = Q.front();
Q.pop();
for(int i = 0;i < 26; i++ )
if(nxt[now][i]==-1)
nxt[now][i] = nxt[fail[now]][i]; /* *** */
else
{
fail[nxt[now][i]] = nxt[fail[now]][i];
Q.push(nxt[now][i]);
}
}
}
/*
创建匹配失败时的转移数组;
根节点与第一层部分匹配失败后转移至根节点部分;
其他部分则转移至该字符出现过的节点位置;
*/
int query(char buf[])
{
int len = strlen(buf);
int now = root;
int res = 0;
for(int i = 0;i < len ; i++ )
{
now = nxt[now][buf[i]-'a'];
int tmp = now;
while( tmp != root)
{
res += end[tmp];
end[tmp] = 0;
tmp = fail[tmp];
}
}
return res;
}
/*
输入需要拿来匹配的串;
一次次的对当前串的各位进行匹配,如果包含当前字符的串在字典树中有出现,则答案累计加上该出现次数,且跳转转移节点进行计数;
因为end记录的是出现的次数,所以在需要匹配的串中如果有该字符,那么进行清0,则能保证字典树中的串在母串中出现一次;
*/
};
Tries AC;
char str[maxn];
int main()
{
int t;
int n;
scanf("%d",&t);
while(t--)
{
AC.init();
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%s",str);
AC.insert(str);
}
AC.build();
scanf("%s",str);
printf("%d\n",AC.query(str));
}
return 0;
}