AC自动机解决字符集很大的情况(可持久化数组优化getfail的过程)

Posted Frozen_Guardian

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AC自动机解决字符集很大的情况(可持久化数组优化getfail的过程)相关的知识,希望对你有一定的参考价值。

今天遇到了一个问题,那就是如果 A C AC AC 自动机的字符集很大该怎么办?比如改成 1 e 5 1e5 1e5 该怎么办呢?

例如下题:
在这里插入图片描述
题目来源转自(侵权删):点击查看

先不考虑解法,肯定是需要用 A C AC AC 自动机去解决的,但是问题是,本题中字符串的总长度可能达到 O ( n 2 ) O(n^2) O(n2) 级别,加上字符集特别大,所以遇到了很多问题

不过不难看出题目中给出的实际上就是一棵 t r i e trie trie 树,我们可以直接在 t r i e trie trie 树上 g e t f a i l getfail getfail,就可以构建出 A C AC AC 自动机了,现在的问题转换为,如何处理字符集很大的情况

先看普通的 g e t f a i l getfail getfail 的模板:

void getfail()
{
	queue<int>q;
	for(int i=0;i<26;i++)
	{
		if(trie[0][i])
		{
			fail[trie[0][i]]=0;
			q.push(trie[0][i]);
		}
	}
	while(!q.empty())
	{
		int cur=q.front();
		q.pop();
		for(int i=0;i<26;i++)
		{
			if(trie[cur][i])
			{
				fail[trie[cur][i]]=trie[fail[cur]][i];
				q.push(trie[cur][i]);
			}
			else
				trie[cur][i]=trie[fail[cur]][i];
		}
	}
}

简单的思路肯定是直接把 26 26 26 改成 1 e 5 1e5 1e5,很可惜这样显然是会 T L E TLE TLE

考虑 b f s bfs bfs 转移的瓶颈出现在哪里呢?通过分析后不难看出实际上就是上面代码第 24 24 24 行的

trie[cur][i]=trie[fail[cur]][i];

的这句代码,转移了太多无用的状态。对于每一层的转移,实际上只有很少的情况进入到了 i f if if 语句里

所以假设,我们开一个 u n o r d e r e d _ m a p < i n t , i n t > t r a n s [ N ] unordered\\_map<int,int>trans[N] unordered_map<int,int>trans[N] 用于储存题目中给出的字典树,其中 t r a n s [ u ] [ t o ] = v trans[u][to]=v trans[u][to]=v 表示在字典树中从点 u u u 经过一条权值为 t o to to 的边可以到达点 v v v。再开一个 u n o r d e r e d _ m a p < i n t , i n t > t r i e [ N ] unordered\\_map<int,int>trie[N] unordered_map<int,int>trie[N] 用于储存 g e t f a i l getfail getfail 的转移过程,则将上述代码改进一下:

void getfail()
{
	queue<int>q;
	for(auto it:trans[0])
	{
		int u=0,v=it.second,to=it.first;
		trie[u][to]=v;
		fail[v]=u;
		q.push(v);
	}
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		trie[u]=trie[fail[u]];
		for(auto it:trans[u])
		{
			int v=it.second,to=it.first;
			trie[u][to]=v;
			fail[trie[u][to]]=trie[fail[u]][to];
			q.push(trie[cur][to]);
		}
	}
}

如此一来,干脆把 i f − e l s e if-else ifelse 分支直接删除掉了。不过又出现了新的问题,如何快速执行第 15 15 15 行的赋值操作呢?

这里就要引入可持久化数组了,所谓可持久化数组,就是可以访问历史版本的,支持单点更新和单点查询的主席树

如此一来就完美解决了这个模型

代码:

const int N=1e6+100;
const int M=1e5;
unordered_map<int,int>trans[N];
struct Node {
	int l,r,val;
}tree[N];
int trie[N],fail[N],tot;
int newnode() {
	tot++;
	tree[tot].l=tree[tot].r=tree[tot].val=0;
	return tot;
}
void add(int &k,int pos,int val,int l,int r) {
	int nk=newnode();
	tree[nk]=tree[k];
	k=nk;
	if(l==r) {
		tree[k].val+=val;
		return;
	}
	int mid=(l+r)>>1;
	if(pos<=mid) {
		add(tree[k].l,pos,val,l,mid);
	} else {
		add(tree[k].r,pos,val,mid+1,r);
	}
}
int ask(int k,int pos,int l,int r) {
	if(l==r) {
		return tree[k].val;
	}
	int mid=(l+r)>>1;
	if(pos<=mid) {
		return ask(tree[k].l,pos,l,mid);
	} else {
		return ask(tree[k].r,pos,mid+1,r);
	}
}
void getfail() {
	queue<int>q;
	for(auto it:trans[0]) {
		int u=0,v=it.second,to=it.first;
		add(trie[u],to,v,1,M);
		fail[v]=u;
		q.push(v);
	}
	while(q.size()) {
		int u=q.front();
		q.pop();
		trie[u]=trie[fail[u]];
		for(auto it:trans[u]) {
			int v=it.second,to=it.first;
			int delta=v-ask(trie[u],to,1,M);
			add(trie[u],to,delta,1,M);
			fail[v]=ask(trie[fail[u]],to,1,M);
			q.push(v);
		}
	}
}
void init() {
	tot=-1;
	newnode();
}

以上是关于AC自动机解决字符集很大的情况(可持久化数组优化getfail的过程)的主要内容,如果未能解决你的问题,请参考以下文章

AC自动机详解(附加可持久化AC自动机)

JZYZOJ1369 [coci2012]覆盖字符串 AC自动机

OI未学知识点或需要加强的部分

ac自动机fail树上dfs序建可持久化线段树

Keywords Search HDU2222 AC自动机模板题

[NOI2011][bzoj2434] 阿狸的打字机 [AC自动机+dfs序+fail树+树状数组]