AC自动机
AC自动机
总结
AC自动机可以说是KMP和Trie树的合体,用于解决多模式串匹配问题
核心过程:
-
建立trie树,插入字符串
-
完成fail指针并完成\(next[i][j]\)(对于j不存在的情况)
void build(){//bfs 完成 fail指针
queue<int> q;
fail[root] = root;//规定fail[root]指向root
for(int i=0;i<26;i++){
if(nxt[root][i]==-1){ nxt[root][i]=root; }//root 下面的空指针全部只想root
else{
fail[nxt[root][i]] = root; //36,38这里保证了47行代码是合理的;
q.push(nxt[root][i]);
}
}
while(!q.empty()){
int now = q.front();
///db(now);
q.pop();
for(int i=0;i<26;i++){
int t = nxt[now][i];
if(t==-1){nxt[now][i] = nxt[fail[now]][i];}//nxt和fail是相互作用的
else {
fail[t] = nxt[fail[now]][i]; // 47,49两行决定了其如同KMP算法,能够将复杂度维持在线性; 仔细思考
q.push(t);
}
}
}
}
- 进行查询,注意统计种类还是次数;
统计时,注意其后缀也可能是字符串
code
int query(char buf[]){//目前反对空串
int len = strlen(buf);
int now = root,cnt = 0;
for(int i=0;i<len;i++){
now = nxt[now][idx(buf[i])];
int t = now;
while(t!=root){
cnt+=end[t];
end[t]=0;//只统计种类
t = fail[t];// 后缀也可能被统计
}
}
return cnt;
}
模板题HDU2222
code
test
#include<bits/stdc++.h>
#include<stdio.h>
#include<iostream>
using namespace std;
#define db(x) cout<<"["<<#x<<"]="<<x<<endl
const int maxm = 1e6+10;
const int maxn = 5e5+20;
char text[maxm],s[60];//s: 模式串
int ca,n;
struct Aho{// 数组形式的AC自动机
int nxt[maxn][26],fail[maxn],end[maxn];//end 用于计数
// nxt[i][j]==-1 表示没有字符 'a'+j
int root,L;//L 控制节点计数
int newp(){
for(int i=0;i<26;i++){nxt[L][i]=-1;} //fail?
end[L]=0, L++;
return L-1;
}
void init(){
L=0;
root = newp();
}
inline int idx(char c){return c - 'a';}
void insert(char p[]){
int len = strlen(p);
int now = root;
for(int i=0;i<len;i++){
int t = nxt[now][idx(p[i])];
if(t==-1){nxt[now][idx(p[i])] = newp();}
now = nxt[now][idx(p[i])];
}
if(now!=root) end[now]++; //空字符串 now == root
}
void build(){//bfs 完成 fail指针
queue<int> q;
fail[root] = root;//规定fail[root]指向root
for(int i=0;i<26;i++){
if(nxt[root][i]==-1){ nxt[root][i]=root; }//root 下面的空指针全部只想root
else{
fail[nxt[root][i]] = root; //36,38这里保证了47行代码是合理的;
q.push(nxt[root][i]);
}
}
while(!q.empty()){
int now = q.front();
///db(now);
q.pop();
for(int i=0;i<26;i++){
int t = nxt[now][i];
if(t==-1){nxt[now][i] = nxt[fail[now]][i];}//nxt和fail是相互作用的
else {
fail[t] = nxt[fail[now]][i]; // 47,49两行决定了其如同KMP算法,能够将复杂度维持在线性; 仔细思考
q.push(t);
}
}
}
}
int query(char buf[]){//目前反对空串
int len = strlen(buf);
int now = root,cnt = 0;
for(int i=0;i<len;i++){
now = nxt[now][idx(buf[i])];
int t = now;
while(t!=root){
cnt+=end[t];
end[t]=0;//只统计种类
t = fail[t];// 后缀也可能被统计
}
}
return cnt;
}
void debug(){
for(int i=0;i<L;i++){
printf("id=%3d fail=%3d end=%3d chi=[",i,fail[i],end[i]);
for(int j=0;j<26;j++){
printf("%c %d,",j+'a',nxt[i][j]);
}
printf("]\n");
}
}
}ac;
int main(){
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
scanf("%d",&ca);
while(ca--){
ac.init();
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%s",s);
ac.insert(s);
}
ac.build();
//ac.debug();
scanf("%s",text);
printf("%d\n",ac.query(text));
}
return 0;
}