AC自动机上学期看过,看是一直没有写过,今天把这道模板题写完了。
附上学习博客的地址:AC自动机
代码风格是模仿的bin神博客的。
分析:理解了KMP以后AC自动机并不是很难理解。KMP是对于一个串找公共前后缀,而AC自动机是对于一颗Trie树找公共前后缀。
要点:如果匹配失败了,那么要转到fail指针后面的进行匹配。
代码中预处理出所有的ch值,这样扫匹配串:ch[now][str[i]-‘a’]就是root…ch[now][str[i]-‘a’]就是当前的公共后缀,这个时候需要匹配有多少个单词是该串子串,那么只需要维护root…ch[now][str[i]-‘a’]的子串就可以了,就是temp值一直改变的原因。
附上代码:
#include <bits/stdc++.h>
#define LL long long
#define FOR(i,x,y) for(int i = x;i < y;++ i)
#define IFOR(i,x,y) for(int i = x;i > y;-- i)
using namespace std;
const int maxn = 10010;
const int maxm = 1000010;
struct AC{
int ch[maxn*52][26],fail[maxn*52],val[maxn*52],sz,rt;
void init(){
sz = rt = 0;
memset(ch[rt],-1,sizeof(ch[rt]));
}
void newnode(){
++ sz;
FOR(i,0,26) ch[sz][i] = -1;
val[sz] = 0;
}
void insert(char* str){
int len = strlen(str);
int u = rt;
FOR(i,0,len){
if(ch[u][str[i]-'a'] == -1){
newnode();
ch[u][str[i]-'a'] = sz;
}
u = ch[u][str[i]-'a'];
}
++ val[u];
}
void build(){
queue <int> q;
FOR(i,0,26){
if(ch[rt][i] == -1){
ch[rt][i] = rt;
}
else{
fail[ch[rt][i]] = rt;
q.push(ch[rt][i]);
}
}
while(!q.empty()){
int u = q.front(); q.pop();
FOR(i,0,26){
if(ch[u][i] == -1){
ch[u][i] = ch[fail[u]][i];
}
else{
fail[ch[u][i]] = ch[fail[u]][i];
q.push(ch[u][i]);
}
}
}
}
int query(char* str){
int len = strlen(str);
int now = rt;
int res = 0;
FOR(i,0,len){
now = ch[now][str[i]-'a'];
int temp = now;
while(temp != rt){
res += val[temp];
val[temp] = 0;
temp = fail[temp];
}
}
return res;
}
}ac;
int n;
char s[maxm];
int main()
{
//freopen("test.in","r",stdin);
int T; scanf("%d",&T);
while(T--){
scanf("%d",&n);
ac.init();
FOR(i,0,n) scanf("%s",s),ac.insert(s);
ac.build();
scanf("%s",s);
int ans = ac.query(s);
printf("%d\n",ans);
}
return 0;
}