【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=1000000; char s[201][N]; struct Trie{ Trie *ch[26],*fail,*last; std::vector<int> mem; }node[N+1],*root,*q[N+1]; int n,sz,sum[201]; inline Trie *newnode(){ return &node[++sz]; } inline void insert(char *w,int id){ Trie *p=root; for(int i=0;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[0]=root; for(int i=0,j=0;i<=j;i++) for(int l=0;l<26;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=1;i<=n;i++) scanf("%s",s[i]),insert(s[i],i); build(); } inline void work(){ for(int k=1;k<=n;k++){ Trie *now=root; for(int i=0;s[k][i];i++){ now=now->ch[s[k][i]-'a']; for(int j=0;j<now->mem.size();j++) sum[now->mem[j]]++; for(Trie *p=now->last;p;p=p->last) for(int j=0;j<p->mem.size();j++) sum[p->mem[j]]++; } } } inline void print(){ for(int i=1;i<=n;i++) printf("%d\n",sum[i]); } int main(){ read(); work(); print(); }
苟利国家生死以, 岂因祸福避趋之。