防止意外爆零 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\) 节点结尾的字符串的数量。

posted @ 2026-08-28 12:45  IterateRabbitwky  阅读(4)  评论(0)    收藏  举报