学习笔记(3)AC自动机
一句话概括,其实就是在 \(Trie\) 树上进行 \(Kmp\) 操作匹配字符串的一种有限状态自动机 \((DFA)\)
在\(AC\)自动机上的每个节点所表示的含义与传统的 \(Trie\) 树略有不同。\(AC\)自动机上的节点表示前缀结尾为该字符的一种状态。\(AC\)自动机在失配时通过找寻另一个字符串匹配的后缀继续进行匹配。通过构建“失配指针”\((fail)\) 建立字典图以在 \(O(n+m)\) 或者 \(O(n)\) 的时间内完成对字符串的匹配与统计
构建失配指针时,若 \(tr[u][i]\) 存在,则将 \(fail[tr[u][i]]\) 指向 \(tr[fail[u]][i]\);否则将 \(tr[u][i]\) 指向 \(tr[fail[u]][i]\)。这样可以避免不停的跳 \(fail\) 指针以寻找满足存在字符 \(i\) 的模式串前缀(即:跳 \(fail\) 后要继续匹配走到的状态,类似于记忆化)
在某些类型的应用下,\(AC\)自动机上的字符串答案统计可以通过打标记 \(+\) 拓扑排序\(/\)子树求和进行优化(通过 \(fail\) 指针构建的字典图可证构成 \(DAG\),于是统计每一个 \(fail\) 节点的入度,统计答案时将 \(fail[u]\) 的答案加上 \(u\) 的答案,避免重复跳 \(fail\) 指针,可以大大提高效率)
具体实现上,需要先利用给出的模式串建立字典树,然后才能进行字符串匹配
模板
\(P3808\) 【模板】AC 自动机(简单版)
纯模板,通过 \(e\) 数组记录以相应字符结尾的前缀数
#include <bits/stdc++.h>
#define N 1000005
using namespace std;
int n;
struct AC{
int tot;
int tr[N][26],fail[N],e[N];
void insert(char *s){
int u=0;
for(int i=1;s[i];i++){
if(!tr[u][s[i]-'a']) tr[u][s[i]-'a']=++tot;
u=tr[u][s[i]-'a'];
}
++e[u];
}
queue<int> q;
void build(){
for(int i=0;i<26;i++){
if(tr[0][i]) q.push(tr[0][i]);
}
while(!q.empty()){
int u=q.front(); q.pop();
for(int i=0;i<26;i++){
if(tr[u][i]){
fail[tr[u][i]]=tr[fail[u]][i];
q.push(tr[u][i]);
}
else tr[u][i]=tr[fail[u]][i];
}
}
}
int query(char *t){
int u=0, res=0;
for(int i=1;t[i];i++){
u=tr[u][t[i]-'a'];
for(int j=u;j && e[j];j=fail[j]){
res+=e[j];
e[j]=0;
}
}
return res;
}
}DFA;
char s[N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%s",s+1);
DFA.insert(s);
}
scanf("%s",s+1);
DFA.build();
printf("%d",DFA.query(s));
return 0;
}
\(P5357\) 【模板】AC 自动机(二次加强版)
拓扑排序优化字典图:
#include <bits/stdc++.h>
#define N 2000005
using namespace std;
int n;
char s[N];
struct node{
int fail,val,flag;
int son[26];
node(){fail=val=flag=0; memset(son,0,sizeof(son));}
};
struct AC{
int tot;
node tr[N];
int id[N],in[N],ans[N];
void insert(char *s,int num){
int u=0;
for(int i=0;s[i];i++){
if(!tr[u].son[s[i]-'a']) tr[u].son[s[i]-'a']=++tot;
u=tr[u].son[s[i]-'a'];
}
if(!tr[u].flag) tr[u].flag=num;
id[num]=tr[u].flag;
}
queue<int> q;
void build(){
for(int i=0;i<26;i++){
if(tr[0].son[i]) q.push(tr[0].son[i]);
}
while(!q.empty()){
int u=q.front(); q.pop();
for(int i=0;i<26;i++){
int v=tr[u].son[i];
if(v){
tr[v].fail=tr[tr[u].fail].son[i];
++in[tr[v].fail];
q.push(v);
}
else tr[u].son[i]=tr[tr[u].fail].son[i];
}
}
}
void query(char *t){
int u=0;
for(int i=0;t[i];i++){
u=tr[u].son[t[i]-'a'];
++tr[u].val;
}
}
void topu(){
for(int i=0;i<=tot;i++){
if(!in[i]) q.push(i);
}
while(!q.empty()){
int u=q.front(); q.pop();
int v=tr[u].fail; --in[v];
ans[tr[u].flag]=tr[u].val;
tr[v].val+=tr[u].val;
if(!in[v]) q.push(v);
}
}
}DFA;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%s",s);
DFA.insert(s,i);
}
DFA.build();
scanf("%s",s);
DFA.query(s);
DFA.topu();
for(int i=1;i<=n;i++) printf("%d\n",DFA.ans[DFA.id[i]]);
return 0;
}