AC自动机,一直以来都以为是一个非常高大上的算法,其实它还真的挺高大上的。
首先来说,ac自动机的思想与kmp类似,需要自己模拟来理解。
给两个博客:
http://www.cppblog.com/menjitianya/archive/2014/07/10/207604.html
https://blog.csdn.net/KXL5180/article/details/88093307
还有bibi上有个挺好的视频可以看一下,有助于理解
板子分为3个部分来:
首先对于需要的数组
int cnt,root;//cnt表示树的某个节点位置;root表示根,其实就是0;
int fail[maxn];//表示上一个他这个字符的位置。
int ch[maxn][30];//用来表示一个新的节点,存的值是下一个字符的位置
int val[maxn];//表示某个节点的有效值,就是以该点串串结尾的个数
1.建树。建立字典树,这里开了静态的空间来装线段树。
int cnt,root; //N=26;
void init()
{
cnt=0;
root=newnode();
}
int newnode()//建立新的一个节点,并初始化fail指针与val值为0;
{
for(int i=0;i<N;i++)
ch[cnt][i]=0;
val[cnt]=fail[cnt++]=0;
return cnt-1;
}
void insert(char *s)//插入某一个字符串
{
int len=strlen(s);
int u=0;
for(int i=0;i<len;i++)
{
int v=s[i]-'a';
if(!ch[u][v])//如果有该点就不开新节点
ch[u][v]=newnode();//没有就开新节点
u=ch[u][v];
}
val[u]++;//每当加完一个新的字符串,结尾其实就是节点要val值加1,表示这点是某个串串的结束
}
2.建立fail指针。
fail指针的意义就是如果找一个点的时候,你可以找他的fail指针找到有相同作用的点,当某个点寻找下一点失败的时候有fail指针引导你下次应该跳转的位置。自己并不能说的很清楚。
可以参考博客:https://blog.csdn.net/u013371163/article/details/60469145
void getfail()
{
queue<int >q;
int u=0;
for(int i=0;i<N;i++)//找到连接root节点的点,加入队列fail指针已经是0了不用重新赋值。
if(ch[u][i])
q.push(ch[u][i]);
while(!q.empty())//类似于bfs的搜索方式
{
u=q.front();
q.pop();
for(int i=0;i<N;i++)
{
if(ch[u][i])//如果u节点有下对应的(i+‘a’)
{
fail[ch[u][i]]=ch[fail[u]][i];//这个点fail指针就是连接到他父亲的fail指针对应位置下的那个'a'+i字母的位置。
//因为假设u点的fail的位置为v,那么v这个点的作用其实同u点,那么ch[v][i]即v下边如果有那个'a'+i字母,那么这个位置之前已经知道了,赋过去就行
//如果没有呢,那么其实之前开辟新节点也是处理过的,fail[cnt][i]=0,那么他的fail指针就是指向0,就是根节点。
q.push(ch[u][i]);
}
else
ch[u][i]=ch[fail[u]][i];//如果u没有'a'+i这个字母,就把它的位置(注意是位置)直接跳到上边解释的位置,查询的时候模拟一下就知道了
}
}
}
3.查找某个串的匹配串有几种。
int query(char *s)//查询的时候是需要自己模拟一下
{
int len=strlen(s);
int u=0,ans=0;
for(int i=0;i<len;i++)
{
int v=s[i]-'a';
u=ch[u][v];//找到u下边的'a'+i字母的位置
for(int j=u;j&&~val[j];j=fail[j])//fail指针走到根节点,或者某个点走过了
{
ans+=val[j];//加上某个节点的值,其实就是加上串尾点
val[j]=-1;//标记为走过
}
}
return ans;
}
AC代码
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<math.h>
#include<set>
#include<stack>
#include<vector>
#include<map>
#include<queue>
#define myself i,l,r
#define lson i<<1
#define rson i<<1|1
#define Lson i<<1,l,mid
#define Rson i<<1|1,mid+1,r
#define half (l+r)/2
#define inff 0x3f3f3f3f
#define lowbit(x) x&(-x)
#define me(a,b) memset(a,b,sizeof(a))
#define min4(a,b,c,d) min(min(a,b),min(c,d))
#define min3(x,y,z) min(min(x,y),min(y,z))
#define max4(a,b,c,d) max(max(a,b),max(c,d))
#define max3(x,y,z) max(max(x,y),max(y,z))
typedef long long ll;
using namespace std;
const int maxn=5e5+5;
const int maxm=1e6+5;
const int N=26;
struct AC
{
int cnt,root;
int fail[maxn];
int ch[maxn][30];
int val[maxn];
int newnode()
{
for(int i=0;i<N;i++)
ch[cnt][i]=0;
val[cnt]=fail[cnt++]=0;
return cnt-1;
}
void init()
{
cnt=0;
root=newnode();
}
void insert(char *s)
{
int len=strlen(s);
int u=0;
for(int i=0;i<len;i++)
{
int v=s[i]-'a';
if(!ch[u][v])
ch[u][v]=newnode();
u=ch[u][v];
}
val[u]++;
}
void getfail()
{
queue<int >q;
int u=0;
for(int i=0;i<N;i++)//找到连接root节点的点,加入队列fail指针已经是0了不用重新赋值。
if(ch[u][i])
q.push(ch[u][i]);
while(!q.empty())//类似于bfs的搜索方式
{
u=q.front();
q.pop();
for(int i=0;i<N;i++)
{
if(ch[u][i])//如果u节点有下对应的(i+‘a’)
{
fail[ch[u][i]]=ch[fail[u]][i];//这个点fail指针就是连接到他父亲的fail指针对应位置下的那个'a'+i字母的位置。
//因为假设u点的fail的位置为v,那么v这个点的作用其实同u点,那么ch[v][i]即v下边如果有那个'a'+i字母,那么这个位置之前已经知道了,赋过去就行
//如果没有呢,那么其实之前开辟新节点也是处理过的,fail[cnt][i]=0,那么他的fail指针就是指向0,就是根节点。
q.push(ch[u][i]);
}
else
ch[u][i]=ch[fail[u]][i];//如果u没有'a'+i这个字母,就把它的位置(注意是位置)直接跳到上边解释的位置,查询的时候模拟一下就知道了
}
}
}
int query(char *s)//查询的时候是需要自己模拟一下
{
int len=strlen(s);
int u=0,ans=0;
for(int i=0;i<len;i++)
{
int v=s[i]-'a';
u=ch[u][v];//找到u下边的'a'+i字母的位置
for(int j=u;j&&~val[j];j=fail[j])//fail指针走到根节点,或者某个点走过了
{
ans+=val[j];//加上某个节点的值,其实就是加上串尾点
val[j]=-1;//标记为走过
}
}
return ans;
}
}AC;
char str[maxm];
int main()
{
int t,n;
cin>>t;
while(t--)
{
scanf("%d",&n);
AC.init();
while(n--)
{
scanf("%s",str);
AC.insert(str);
}
AC.getfail();
scanf("%s",str);
printf("%d\n",AC.query(str));
}
return 0;
}