洛谷 P5357 【模板】AC自动机(二次加强版)
一、题目:
二、思路:
(我们将文本串叫做文章,模式串叫做单词。)
那么这道题是个AC自动机的优化。我从题解上可以看出,这可能是个比较普通的优化。所以我决定再写一篇博客。
那么我么考虑在匹配文章的过程中,我们是要不断跳到fail指针上去,而且一个点可能会被跳多次。那么这道题复杂度就爆炸了。
考虑如何减少跳的次数。我们发现,所有的fail边同样构成了一棵树。那么如果我们记录下来每个节点在匹配文章的过程中被访问的次数num。那么我们发现在跳fail的过程中,i节点一共被访问的次数应该等于它的num加上所有指向它的fail指针的起点的num。语言表达可能有点不清楚,我们用公式表达。
\[i节点一共被访问的次数=num_i+\sum_{fail[j]=i}num_j
\]
如果i节点是一个单词的末尾,那么i节点一共被访问的次数就是该单词在文章中出现的次数。
所以这个问题就变得非常简单了。随便dfs一下,也可以直接从下往上更新一遍(拓扑排序)就可以了。
三、代码:
#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
const int maxn=2e5+5;
int n;
string T[maxn],S;
int trie[maxn][30],sz,num[maxn*30],order[maxn],vis[maxn];
int tag[maxn*30],fail[maxn*30];
int in[maxn*30];
inline void insert(string s,int id){
int p=0;
for(register int i=0;i<s.size();++i){
if(!trie[p][s[i]-'a'+1])trie[p][s[i]-'a'+1]=++sz;
p=trie[p][s[i]-'a'+1];
}
if(!tag[p])tag[p]=id;
order[id]=tag[p];
}
inline void bfs(void){
queue<int>q;
for(register int i=1;i<=26;++i){
if(trie[0][i])q.push(trie[0][i]),fail[trie[0][i]]=0;
}
while(q.size()){
int x=q.front();q.pop();
for(register int i=1;i<=26;++i){
if(trie[x][i]){fail[trie[x][i]]=trie[fail[x]][i];in[fail[trie[x][i]]]++;q.push(trie[x][i]);}
else trie[x][i]=trie[fail[x]][i];
}
}
}
inline void query(void){
int p=0;
for(register int i=0;i<S.size();++i){
p=trie[p][S[i]-'a'+1];
++num[p];
}
}
inline void topsort(void){
queue<int>q;
for(register int i=1;i<=sz;++i){
if(!in[i])q.push(i);
}
while(q.size()){
int x=q.front();q.pop();
vis[tag[x]]=num[x];
int y=fail[x];
num[y]+=num[x];
in[y]--;
if(!in[y])q.push(y);
}
}
int main(){
cin>>n;
for(register int i=1;i<=n;++i){
cin>>T[i];
insert(T[i],i);
}
cin>>S;
bfs();
query();
topsort();
for(register int i=1;i<=n;++i){
printf("%d\n",vis[order[i]]);
}
return 0;
}