前言
AC自动机是一种多模匹配算法。给出一个目标T和多个模式P1、P2、P3、…、pn,问你有多少个模式在T中出现过,并给出在T中匹配的位置。
复杂度O(n),n为T的长度
步骤
构造Trie树
构造一棵Trie树,作为AC自动机算法的数据结构。将多个模式Pi插入Trie树中。不仅有此前Trie树的性质,节点还增加一个fail指针。如果当前点匹配失败,则将指向当前匹配的字符的指针转移到fail指针指向的地方,使得当前匹配的模式串的后缀和fail指针指向的模式串的前缀相同。
void insert_(char *str)
{
int len=strlen(str),root=0;
for(int i=0;i<len;i++){
int next=str[i]-'a';
if(!trie[root][next]) trie[root][next]=++tot;
root=trie[root][next];
}
cntword[root]++;
}
通过BFS构造fail指针
root进入队列,出队。根绝后缀的定义,root的孩子节点的fail都指向root,root的孩子节点入队。其他节点now若有第i个子孩子,将第i个子孩子的fail指针指向now节点的fail指向节点的第i个孩子.因为我们不能漏掉任何一种情况,所以从后缀最大的开始;否则我们就将这个子节点指向now节点fail的第i个子节点
void getFail()
{
queue<int> q;
for(int i=0;i<26;i++){
if(trie[0][i]){
fail[trie[0][i]]=0;
q.push(trie[0][i]);
}
}
while(!q.empty()){
int now=q.front();
q.pop();
for(int i=0;i<26;i++){
if(trie[now][i]){
fail[trie[now][i]]=trie[fail[now]][i];
q.push(trie[now][i]);
}
else
trie[now][i]=trie[fail[now]][i];
}
}
}
扫描目标进行匹配
int query(char *str){
int now=0,ans=0,len=strlen(str);
for(int i=0;i<len;i++){
now=trie[now][str[i]-'a'];
for(int j=now;j&&cntword[j]!=-1;j=fail[j])
{
ans+=cntword[j];
cntword[j]=-1;
}
}
return ans;
}
模板
#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int maxn=1e6+10;
int trie[maxn][26];
int flagg[maxn];
int fail[maxn];
int tot;
char s[maxn];
int newNode(){
for(int i=0;i<26;i++){
trie[tot][i]=0;
}
flagg[tot]=0;
return tot++;
}
void insert_(char *str)
{
int len=strlen(str),root=0;
for(int i=0;i<len;i++){
int next=str[i]-'a';
if(!trie[root][next]) trie[root][next]=newNode();
root=trie[root][next];
}
flagg[root]++;
}
void getFail()
{
queue<int> q;
for(int i=0;i<26;i++){
if(trie[0][i]){
fail[trie[0][i]]=0;
q.push(trie[0][i]);
}
}
while(!q.empty()){
int now=q.front();
q.pop();
for(int i=0;i<26;i++){
if(trie[now][i]){
fail[trie[now][i]]=trie[fail[now]][i];
q.push(trie[now][i]);
}
else
trie[now][i]=trie[fail[now]][i];
}
}
}
int query(char *str){
int now=0,ans=0,len=strlen(str);
for(int i=0;i<len;i++){
now=trie[now][str[i]-'a'];
for(int j=now;j&&flagg[j]!=-1;j=fail[j])
{
ans+=flagg[j];
flagg[j]=-1;
}
}
return ans;
}
int main()
{
}
模板题
#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int maxn=1e6+10;
int trie[maxn][26];
int flagg[maxn];
int fail[maxn];
int tot;
char s[maxn];
int newNode(){
for(int i=0;i<26;i++){
trie[tot][i]=0;
}
flagg[tot]=0;
return tot++;
}
void insert_(char *str)
{
int len=strlen(str),root=0;
for(int i=0;i<len;i++){
int next=str[i]-'a';
if(!trie[root][next]) trie[root][next]=newNode();
root=trie[root][next];
}
flagg[root]++;
}
void getFail()
{
queue<int> q;
for(int i=0;i<26;i++){
if(trie[0][i]){
fail[trie[0][i]]=0;
q.push(trie[0][i]);
}
}
while(!q.empty()){
int now=q.front();
q.pop();
for(int i=0;i<26;i++){
if(trie[now][i]){
fail[trie[now][i]]=trie[fail[now]][i];
q.push(trie[now][i]);
}
else
trie[now][i]=trie[fail[now]][i];
}
}
}
int query(char *str){
int now=0,ans=0,len=strlen(str);
for(int i=0;i<len;i++){
now=trie[now][str[i]-'a'];
for(int j=now;j&&flagg[j]!=-1;j=fail[j])
{
ans+=flagg[j];
flagg[j]=-1;
}
}
return ans;
}
int main()
{
int t,n;
scanf("%d",&t);
while(t--){
tot=0;
newNode();
scanf("%d",&n);
while(n--){
scanf("%s",s);
insert_(s);
}
getFail();
scanf("%s",s);
printf("%d\n",query(s));
}
return 0;
}
感想
因为这位博主Trie树的建立方式跟我的一样,所以代码选择了他的。也看了B站很多视频,但还是感觉有很多地方需要多体会。