前置技能
- 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
个模式串,平均长度为;文章长度为
m
m
。
建立Trie树:
建立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();
}