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链前缀和。上面那个题没有怎么用。
这道题用了这个优化。还有这个同时也是二进制分组维护动态的静态数据结构,有兴趣可以了解一下。