关于AC自动机的查询操作
查看原帖
关于AC自动机的查询操作
674049
werio45楼主2023/10/7 09:53

我的大致思路是先把文本串放到AC自动机上去并标记经过的点,然后再查询每一个模式串在fail树的子树中有没有被标记过。如果有,那就证明这个模式串在文本串中出现过。
用的是暴力insert文本串和暴力遍历fail树的子树,已过。
但看题解区几乎没人这么做,有没有大神来讲解一下题解的查询和我的查询有什么区别,包括时间复杂度上有没有差异。
CodeCode

#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;
}
2023/10/7 09:53
加载中...