【BZOJ 3172】[Tjoi2013]单词 AC自动机

时间:2023-03-09 03:00:26
【BZOJ 3172】[Tjoi2013]单词 AC自动机

关于AC自动机:一个在kmp与Trie的基础上建立的数据结构,关键在于Trie树结构与fail指针,他们各有各的应用。在AC自动机里最典型的就是多串匹配,原本效率为O(n*l+n*l+m*l),(n是模式串个数,m是匹配串长度,l是模式串平均长度),那么他的效率甚至有时不如多个kmp,虽然很好被卡但平均效率还是不错的。如果在此基础上加上last指针,作用为减少匹配过程中不必要的跳fail,那么就会好一些,但是效率不会有显著的提升而且仍然很好卡,目前为O(n*l+n*l+m*n)。在此基础上我们就可以加上Trie图,Trie图作用就是加快建fail以及在匹配过程中的转移,这时效率就会有不错的提升,建fail的时间复杂度也就更有保证,但是仍然会被卡,目前为O(n*l+n*l+m*n)。到此为止我们的时间复杂度已经是比较优秀的了,但是我们还可以让他更加优秀,我们可以在加上fail树,关于fail树,那么我们在匹配过程中就可以省去跳跃的过程,现在我们的时间复杂度已经到了O(n*l+n*l+m+n*l),已经有了巨大的飞跃,而且基本不会被卡。

我的程序是倒数第二层优化的,慢死。

#include <vector>
#include <cstring>
#include <cstdio>
#include <iostream>
const int N=;
char s[][N];
struct Trie{
Trie *ch[],*fail,*last;
std::vector<int> mem;
}node[N+],*root,*q[N+];
int n,sz,sum[];
inline Trie *newnode(){
return &node[++sz];
}
inline void insert(char *w,int id){
Trie *p=root;
for(int i=;w[i];i++){
if(p->ch[w[i]-'a']==NULL)p->ch[w[i]-'a']=newnode();
p=p->ch[w[i]-'a'];
}
p->mem.push_back(id);
}
inline void build(){
q[]=root;
for(int i=,j=;i<=j;i++)
for(int l=;l<;l++)
if(q[i]->ch[l]){
q[++j]=q[i]->ch[l];
q[j]->fail=q[i]==root?root:q[i]->fail->ch[l];
q[j]->last=q[j]->fail->mem.size()?q[j]->fail:q[j]->fail->last;
}
else
q[i]->ch[l]=q[i]==root?root:q[i]->fail->ch[l];
}
inline void read(){
scanf("%d",&n),root=node;
for(int i=;i<=n;i++)
scanf("%s",s[i]),insert(s[i],i);
build();
}
inline void work(){
for(int k=;k<=n;k++){
Trie *now=root;
for(int i=;s[k][i];i++){
now=now->ch[s[k][i]-'a'];
for(int j=;j<now->mem.size();j++)
sum[now->mem[j]]++;
for(Trie *p=now->last;p;p=p->last)
for(int j=;j<p->mem.size();j++)
sum[p->mem[j]]++;
}
}
}
inline void print(){
for(int i=;i<=n;i++)
printf("%d\n",sum[i]);
}
int main(){
read();
work();
print();
}