AC自动机

AC自动机

其实是trie树的改图。每次状态转移在末尾增添一个字符。

其使用失配指针 \(fail\) 指向自动机中自己的最长后缀,以达到多模匹配的效果。

模板题 代码:

其实也不多说了,就是trie加一个fail指针。

其中fail指针如何求?我们遍历全图,当遍历到一条链的第二个点时,我们在初始状态位置向下找到是否有与第二条边相同的字符,然后更新fail即可。其他情况差不多,只不过是从上一次的fail更新接下来的fail,具体见代码。

struct AC_auto_machine{
	int ch[N][26],tot;
	ll val[N];
	int fail[N];
	int deg[N];
	inline int ins(char *s){
		int len=strlen(s+1),p=0;
		for(int i=1;i<=len;++i){
			int c=s[i]-'a';
			if(!ch[p][c]){
				ch[p][c]=++tot;
			}
			p=ch[p][c];
		}
		val[p]=1;
		return p;
	}
	inline void build(){
		static std::queue<int>q;
		while(!q.empty() ) q.pop();
		for(int i=0;i<26;++i){
			if(ch[0][i]) q.push(ch[0][i]);
		}
		while(!q.empty() ){
			int u=q.front();q.pop();
			for(int i=0;i<26;++i){
				if(!ch[u][i]) ch[u][i]=ch[fail[u] ][i];
				else{
					fail[ch[u][i] ]=ch[fail[u] ][i],q.push(ch[u][i]);
					++deg[fail[ch[u][i] ] ];
				}
			}
		}
	}
	inline void query(char *s){
		int len=strlen(s+1),p=0;
		for(int i=1;i<=len;++i){
			int c=s[i]-'a';
			p=ch[p][c];
			++cnt[p];
		}
	}
	inline void topo(){
		static std::queue<int>q;
		while(!q.empty() ) q.pop();
		for(int i=1;i<=tot;++i){
			if(!deg[i]) q.push(i);
		}
		while(!q.empty() ){
			int u=q.front();q.pop();
			int v=fail[u];
			--deg[v];
			cnt[v]+=cnt[u];
			if(!deg[v]) q.push(v);
		}
	}
};

发现有一些地方写得和上面不符合?算是加了些许优化吧。(下面有些提到的上面并没有完全展现出来)

优化1

发现如果匹配fail时,fail上存在加入一条新边得到最长后缀,但是其本身在图上没有这条边可以走。我们直接把它连边是一种不错的解决方案。本质上是当场失配当场跳fail。

优化2

拓扑排序统一处理fail链后缀和(从最大后缀到最小后缀)。把大的处理完再把权值扔给小的即可。

优化3

统计fail链前缀和。上面那个题没有怎么用。

CF710F String Set Queries

这道题用了这个优化。还有这个同时也是二进制分组维护动态的静态数据结构,有兴趣可以了解一下。

solution

posted @ 2022-02-24 11:51  cbdsopa  阅读(21)  评论(0编辑  收藏  举报