欢迎访问 生活随笔!

生活随笔

当前位置: 首页 >

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

发布时间:2024/4/11 41 豆豆
生活随笔 收集整理的这篇文章主要介绍了 AC自动机解决字符集很大的情况(可持久化数组优化getfail的过程) 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

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

例如下题:

题目来源转自(侵权删):点击查看

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

不过不难看出题目中给出的实际上就是一棵 trietrietrie 树,我们可以直接在 trietrietrie 树上 getfailgetfailgetfail,就可以构建出 ACACAC 自动机了,现在的问题转换为,如何处理字符集很大的情况

先看普通的 getfailgetfailgetfail 的模板:

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]);}elsetrie[cur][i]=trie[fail[cur]][i];}} }

简单的思路肯定是直接把 262626 改成 1e51e51e5,很可惜这样显然是会 TLETLETLE

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

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

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

所以假设,我们开一个 unordered_map<int,int>trans[N]unordered\_map<int,int>trans[N]unordered_map<int,int>trans[N] 用于储存题目中给出的字典树,其中 trans[u][to]=vtrans[u][to]=vtrans[u][to]=v 表示在字典树中从点 uuu 经过一条权值为 tototo 的边可以到达点 vvv。再开一个 unordered_map<int,int>trie[N]unordered\_map<int,int>trie[N]unordered_map<int,int>trie[N] 用于储存 getfailgetfailgetfail 的转移过程,则将上述代码改进一下:

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]);}} }

如此一来,干脆把 if−elseif-elseifelse 分支直接删除掉了。不过又出现了新的问题,如何快速执行第 151515 行的赋值操作呢?

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

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

代码:

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的过程)的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。