昨天学习了AC自动机,有点感触,记下来以后遇到相同的问题有个参照的地方。
AC自动机适用于多模式串的匹配问题,其算法的主要特点是只用走一次母串就可以得出所有答案,属于离线算法。算法的核心是改造字典树,个人认为AC自动机属于数据结构的一种,通过同一模式串和不同模式串之间的前缀和后缀的关系对字典树进行增边来达到不需要重复的遍历母串的目的,缺点和字典数一样,面对过大的数据很容易造成内存溢出。
以上是我对算法思想的理解,下面是我对代码的一些理解
void insert(string a)
{
int u=0;
for(int i=0;i<a.size();i++)
{
int v=a[i]-'a';
if(!tree[u][v])
tree[u][v]=++cnt;
u=tree[u][v];
}
book[u]++;
}
简单的字典树插入操作,不用多说;
void getfail()
{
queue<int> que;
for(int i=0;i<26;i++)
if(tree[0][i])
que.push(tree[0][i]);
while(!que.empty())
{
int now=que.front();
que.pop();
for(int i=0;i<26;i++)
{
if(tree[now][i])
{
fail[tree[now][i]]=tree[fail[now]][i];
que.push(tree[now][i]);
}
else
tree[now][i]=tree[fail[now]][i];
}
}
}
对上面的字典树进行递推增边操作,因为数组初始化为0,指向空的边都指向根节点,个人认为这个操作的精髓是
tree[now][i]=tree[fail[now]][i];
如果说fail数组存的是虚边,那么这步操作就是建立了一条实边,为状态的转移做了铺垫。这一步也把字典树变成了“字典图”,可能成环。
int getans(string a)
{
int ans=0;
int now=0;
for(int i=0;i<a.size();i++)
{
now=tree[now][a[i]-'a'];
for(int j=now;j&&book[j]!=-1;j=fail[j])
{
ans+=book[j];
book[j]=-1;
}
}
return ans;
}
获取答案的操作:now指针指向当前走到了那个地方,从这个函数也能清晰的看出tree数组是用来进行状态转移的,fail数组只是用来拓展答案的,不影响当前状态。
下面是完整代码,要注意的是,一般AC自动机十分卡时间,string,cincout最好不要用哪怕是加了iOS::sync_with_stdio(false)也可能会超时 (苟东西)
#include <iostream>
#include <cstring>
#include <queue>
#define MAX 1000010
using namespace std;
int tree[MAX][26]; //字典树
int book[MAX]; //标记数组
int fail[MAX]; //增(虚)边数组
int cnt;
void init()
{
cnt=0;
memset(fail,0,sizeof(fail));
memset(book,0,sizeof(book));
memset(tree,0,sizeof(tree));
}
void insert(string a)
{
int u=0;
for(int i=0;i<a.size();i++)
{
int v=a[i]-'a';
if(!tree[u][v])
tree[u][v]=++cnt;
u=tree[u][v];
}
book[u]++;
}
void getfail()
{
queue<int> que;
for(int i=0;i<26;i++)
if(tree[0][i])
que.push(tree[0][i]);
while(!que.empty())
{
int now=que.front();
que.pop();
for(int i=0;i<26;i++)
{
if(tree[now][i])
{
fail[tree[now][i]]=tree[fail[now]][i];
que.push(tree[now][i]);
}
else
tree[now][i]=tree[fail[now]][i];
}
}
}
int getans(string a)
{
int ans=0;
int now=0;
for(int i=0;i<a.size();i++)
{
now=tree[now][a[i]-'a'];
for(int j=now;j&&book[j]!=-1;j=fail[j])
{
ans+=book[j];
book[j]=-1;
}
}
return ans;
}
int main()
{
init();
string a;
while(cin>>a,a!="end")
insert(a);
cin>>a;
fail[0]=0;
getfail();
cout<<getans(a);
return 0;
}
对数据结构的活用是一项十分重要的能力,其前提是掌握数据结构的构造原理和其成员的各项功能。
未来的路还有很长,加油!