什么是AC自动机??
- AC自动机可以说是KMP的一个强化版,KMP是用来求两个字符串之间的匹配,而AC自动机则是用来求一堆字符串与一个字符串之间的匹配,思路与KMP相似,但却需要用到一些数据结构来维护字符(字典序)‘
在AC自动机中,我们需要用到trie树(字典树)以及KMP的思路,也就是说,我们需要用trie树来维护KMP,或者说在树上做KMP。
在AC自动机中,有一个很重要的一个数组,它就是fail数组,fail数组对于实现AC自动机有着莫大的意义。
fail数组表示什么???
- 我们需要将思路转化到trie树上,fail[i] 表示后缀为 s [ x ] [ i . . n ] s[x][i..n] s[x][i..n](注:这里的n不是指有多少个字符串,而是指trie中∈i的分枝的最深的深度) = = s [ y ] [ k . . w ] ==s[y][k..w] ==s[y][k..w]的最小k的值(其中 x , y ∈ [ 0..25 ] x,y∈[0..25] x,y∈[0..25],因为trie树可以认为是26叉树,指的是分在第几叉),指的是后缀的最长公共前缀的那个节点的编号,使用这个数组能够很好的在树上进行KMP操作
下面有一张很好的图来解释
f
a
i
l
fail
fail数组的含义:
其中:
f
a
i
l
[
4
]
fail[4]
fail[4]所拥有的最长公共前缀就是8号节点
f
a
i
l
[
3
]
fail[3]
fail[3]所对应的是
c
d
cd
cd,最长公共前缀是7号,也是
c
d
cd
cd
上图就是这个意思,每个点都连向了它的fail,大家感性理解便可
如何转移fail?
- 分两种情况,如果有下一个节点,那么就令 f a i l [ c [ n o w ] [ i ] ] = c [ f a i l [ n o w ] ] [ i ] fail[c[now][i]] = c[fail[now]][i] fail[c[now][i]]=c[fail[now]][i];否则就让 c [ n o w ] [ i ] = c [ f a i l [ n o w ] ] [ i ] c[now][i] = c[fail[now]][i] c[now][i]=c[fail[now]][i]
- 比较好理解,大家自行理解
这里AC自动机是需要用用trie树实现的,我们这里来说一说如何建trie树。
如何建树
- 读入一个字符串,我们扫每一个字符,并且判断当前层是否出现过当前的字符,如果没出现,就建立一个点,继续往下扫,否则就继续往下扫。
Code
void ins(string s){
int len = s.size();
int now = 0;
for (int i=0;i<len;i++){
if (!c[now][s[i]-'a']) c[now][s[i]-'a'] = ++cnt;//如果没出现,就加一个节点
now = c[now][s[i] - 'a'];//往下扫
}
val[now] ++;
}
求fail数组
我们已经知道了求fail的大概思想,这是想如何实现。
我们尝试用队列维护,把每个节点存入队列从上往下遍历,做一遍更新即可
Code
void build(){
queue < int > q;
for (int i=0;i<26;i++)
if (c[0][i]) q.push(c[0][i]),fail[c[0][i]] = 0;/先将第0层的给搞出来
while (!q.empty()){
int now = q.front();q.pop();
for (int i=0;i<26;i++)
if (c[now][i]) fail[c[now][i]] = c[fail[now]][i],q.push(c[now][i]);//如果有的话,转移,入队
else c[now][i] = c[fail[now]][i];//否则就令下一个节点到它下一个后缀的节点中
}
}
求答案
- 接下来就是求答案了
- 我们遍历一遍模式串,找出它每一位对应的在树上的节点,不断累加、遍历即可。
int ask(string s){
int now = 0 , ans = 0;
int len = s.size();
for (int i=0;i<len;i++){
int ch = s[i] - 'a';
now = c[now][ch];
int t = now;
for (;t;t = fail[t]) ans += val[t];//利用fail数组不断跳跃求和
}
return ans;
}
这样便基本能够实现AC自动机了。
接下来给大家上几道AC自动机模板题。
一、AC自动机模板题1
给定n个模式串和1个文本串,求有多少个模式串在文本串里出现过。
Solution
这题基本上就是AC自动机模板,只不过需要注意的是这里求的是有多少个模式串出现过,而不是出现了几次,所以我们在遍历的时候打一个标记即可。
#include<bits/stdc++.h>
using namespace std;
int n;
struct Tr{
int c[1010010][27];
int cnt = 0;
int val[10101000];
int fail[10100100];
void ins(string s){
int len = s.size();
int now = 0;
for (int i=0;i<len;i++){
if (!c[now][s[i]-'a']) c[now][s[i]-'a'] = ++cnt;
now = c[now][s[i] - 'a'];
}
val[now] ++;
}
void build(){
queue < int > q;
for (int i=0;i<26;i++)
if (c[0][i]) q.push(c[0][i]),fail[c[0][i]] = 0;
while (!q.empty()){
int now = q.front();q.pop();
for (int i=0;i<26;i++)
if (c[now][i]) fail[c[now][i]] = c[fail[now]][i],q.push(c[now][i]);
else c[now][i] = c[fail[now]][i];
}
}
int ask(string s){
int now = 0 , ans = 0;
int len = s.size();
for (int i=0;i<len;i++){
int ch = s[i] - 'a';
now = c[now][ch];
int t = now;
for (;t && val[t]!=-1;t = fail[t]) ans += val[t] , val[t] = -1;
}
return ans;
}
};
Tr tr;
int main(){
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
string S;
scanf("%d",&n);
for (int i=1;i<=n;i++)
cin>>S,tr.ins(S);
tr.build();
cin>>S;
printf("%d",tr.ask(S));
return 0;
}
二、AC自动机模板题2
有N个由小写字母组成的模式串以及一个文本串T。每个模式串可能会在文本串中出现多次。你需要找出哪些模式串在文本串T中出现的次数最多。
Solution
这道也算是一个裸题了。
在建树的时候打个标记,看当前结尾的字符串是第几个串,统计的额时候累加即可
Code
#include<bits/stdc++.h>
using namespace std;
int n;
string ss[10100];
struct Tr{
int cnt;
int fail[152032];
int c[100100][26];
int num[101000];
int numn[101000];
int maxx = 0;
int len=0;
void mem(){
cnt = 0,len=0,maxx=0;
memset(fail,0,sizeof fail);
memset(num,0,sizeof num);
memset(numn,0,sizeof numn);
memset(c,0,sizeof c);
}
void ins(string s,int Num){
int len = s.size();
int now = 0;
for (int i=0;i<s.size();i++){
int ch = s[i]-'a';
if (!c[now][ch]) c[now][ch] = ++cnt;
now = c[now][ch];
}
// val[now] ++;
num[now] = Num;
}
void build(){
queue < int > q;
for (int i=0;i<26;i++)
if (c[0][i]) fail[c[0][i]] = 0,q.push(c[0][i]);
while (!q.empty()){
int now = q.front();q.pop();
for (int i=0;i<26;i++)
if (c[now][i]) fail[c[now][i]] = c[fail[now]][i],q.push(c[now][i]);
else c[now][i] = c[fail[now]][i];
}
}
void ask(string s){
int len = s.size();
int now = 0;
for (int i=0;i<s.size();i++){
int ch = s[i] - 'a';
now = c[now][ch];
int t=now;
for (;t;t = fail[t]) numn[num[t]] += 1;
}
for (int i=1;i<=n;i++)
if (numn[i] > maxx) maxx = numn[i];
printf("%d\n",maxx);
for (int i=1;i<=n;i++)
if (numn[i] == maxx) cout<<ss[i]<<endl;
}
};
Tr tr;
void work(){
string S;
tr.mem();
for (int i=1;i<=n;i++)
cin>>ss[i],tr.ins(ss[i],i);
tr.build();
cin>>S;
tr.ask(S);
}
int main(){
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
while (cin>>n && n) work();
}