防止意外爆零 1:有关 Trie 的一点小坑
这是一份非常正常的 Trie 树代码,它可以求出 \(s_{1}\) 至 \(s_n\) 的所有字符串中含有某个前缀的字符串的数量。
也就是这道题。
#include <bits/stdc++.h>
using namespace std;
int n,q;
string s;
const int N=3e6+7;
namespace trie{
int cg(char c){
if(islower(c)) return c-'a'+1;
if(isupper(c)) return c-'A'+27;
return c-'0'+53;
}
int te[N][77],cnt[N];
int tnt=0;
void init(){
for(int i=0;i<=trie::tnt;i++){
for(int j=0;j<=75;j++){
trie::te[i][j]=0;
}
trie::cnt[i]=0;
}
trie::tnt=0;
}
void add(string s){
int now=0;
int L=s.size();
for(int i=0;i<L;i++){
int e=cg(s[i]);
if(te[now][e]==0){
te[now][e]=(++trie::tnt);
}
now=te[now][e];
cnt[now]++;
}
}
int query(string s){
int L=s.size();
int now=0;
for(int i=0;i<L;i++){
int e=cg(s[i]);
if(te[now][e]==0) return 0;
now=te[now][e];
}
return cnt[now];
}
}
int main(){
cin.tie(0)->sync_with_stdio(0);
int t;
cin>>t;
while(t--){
cin>>n>>q;
trie::init();
for(int i=1;i<=n;i++){
cin>>s;
trie::add(s);
}
for(int i=1;i<=q;i++){
cin>>s;
cout<<trie::query(s)<<'\n';
}
}
return 0;
}
但是当你学到 AC 自动机的时候,你会发现 AC 自动机的 Trie 是这样写的(请关注 insert 函数与上一份代码的 add 函数的不同):
struct ACAM{
int trie[N][30],fail[N],cnt[N];
int tot=0;
void init(){
memset(trie,0,sizeof(trie));
tot=0;
memset(fail,0,sizeof(fail));
memset(cnt,0,sizeof(cnt));
}
void insert(string &s){
int now=0;
for(int i=0;s[i];i++){
int cg=s[i]-'a';
if(!trie[now][cg]){
trie[now][cg]=++tot;
}
now=trie[now][cg];
}
cnt[now]++;
}
void build(){
queue<int> q;
for(int i=0;i<26;i++){
if(trie[0][i]){
q.push(trie[0][i]);
fail[trie[0][i]]=0;
}
}
while(!q.empty()){
int now=q.front();
q.pop();
for(int i=0;i<26;i++){
if(trie[now][i]){
int vis=trie[now][i];
fail[vis]=trie[fail[now]][i];
q.push(vis);
}
else{
trie[now][i]=trie[fail[now]][i];
}
}
}
}
int query(string &s){
int now=0,ans=0;
for(int i=0;s[i];i++){
int e=s[i]-'a';
now=trie[now][e];
for(int p=now;p&&cnt[p]!=-1;p=fail[p]){
ans+=cnt[p];
cnt[p]=-1;
}
}
return ans;
}
}ac;
二者不同的原因应该是显而易见的:第一份代码中 \(cnt_i\) 存储的是以根到 \(i\) 节点为前缀的字符串的数量,第二份存的是以 \(i\) 节点结尾的字符串的数量。

浙公网安备 33010602011771号