我的大致思路是先把文本串放到AC自动机上去并标记经过的点,然后再查询每一个模式串在fail树的子树中有没有被标记过。如果有,那就证明这个模式串在文本串中出现过。
用的是暴力insert文本串和暴力遍历fail树的子树,已过。
但看题解区几乎没人这么做,有没有大神来讲解一下题解的查询和我的查询有什么区别,包括时间复杂度上有没有差异。
Code
#include<bits/stdc++.h>
#define int long long
#define FOR(i,l,r) for(int i=(l);i<=(r);++i)
using namespace std;
const int TrieS=26+5,TrieN=1e6+10;//字符集大小,节点个数
struct Trie{
int trie[TrieN][TrieS],cnt;
int vis[TrieN];
int insert(string s){
int pos=0;
FOR(i,0,(int)(s.size())-1){
int ch=s[i]-'a';
if(!trie[pos][ch]) trie[pos][ch]=++cnt;//新建一个节点
pos=trie[pos][ch];
}
return pos;
}
};
const int N=1e6+10;
int n,id[N];
namespace AC{
Trie T;
int fail[TrieN];//每一个Trie的节点都有一个fail指针
vector<int>G[TrieN];
//默认Trie根节点为0
void build(){ //建立AC自动机
queue<int>q;
FOR(i,0,25) if(T.trie[0][i]) q.push(T.trie[0][i]);
while(!q.empty()){
int x=q.front(); q.pop();
FOR(c,0,25){ //枚举当前节点的所有儿子
if(T.trie[x][c])
fail[T.trie[x][c]]=T.trie[fail[x]][c], q.push(T.trie[x][c]),
G[T.trie[fail[x]][c]].push_back(T.trie[x][c]);
else
T.trie[x][c]=T.trie[fail[x]][c];
}
}
}
void ins(string s){
//让文本串在AC自动机上跑一遍
int pos=0;
FOR(i,0,(int)(s.size())-1){
int ch=s[i]-'a';
pos=T.trie[pos][ch];
T.vis[pos]++;
}
}
int dfs(int x){//dfs(x)表示以x为根节点的fail树的子树和
int ans=T.vis[x];
for(int v:G[x]) ans+=dfs(v);
return ans;
}
int query(string s){
//查询字符串s中有多少个模式串
ins(s);
int res=0;
FOR(i,1,n) res+=bool(dfs(id[i]));
return res;
}
};//namespace AC
signed main(){
cin>>n;
FOR(i,1,n){
string s;cin>>s;
id[i]=AC::T.insert(s);
}
AC::build();
string t; cin>>t;
cout<<AC::query(t)<<'\n';
return 0;
}