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 if−else 分支直接删除掉了。不过又出现了新的问题,如何快速执行第 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的过程)的主要内容,如果未能解决你的问题,请参考以下文章
JZYZOJ1369 [coci2012]覆盖字符串 AC自动机