AC自动机
首先我们知道给定单个模式串和单个文本串来匹配可以
构建
类似
首先我们将所有的字符串扔到trie里。然后开始构建我们的
啥没看懂?盗张图。
如图所示,以最左边的子树为例,字符串
然后考虑如何快速构建出整个自动机。暴力找最长后缀的复杂度显然是不能接受的,于是我们考虑一点人类智慧的方法。具体的,我们可以修改一下
首先我们通过
- 不存在:我们修改
的结构,直接使 ,也就是在之前的基础上扩展一个字符。这一步将空余位置连到了 指针的对应状态,从而使得下次失配的时候如果跳到这个节点那么可以直接找到 指针,而不需要重复跳。 - 存在:我们将
的 指针变成 。这里我们不需要通过一直跳 指针来找到最长的后缀,因为我们有上一步修改字典树结构的操作。如果在 失配,则我们会跳转到他的 指针。而我们用字典树的空余位置直接存下了下一次会跳到哪一个指针,实现了加速。最后将它入队。
如果实在难以理解可以找oiwiki的动图慢慢看,或者自己画一下图。实在不行可以背,反正也挺好背的。
查询其实也很简单,类似
到此,我们切掉了这个板子。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <queue>
#include <cstring>
using namespace std;
int n,num,trie[500010][26],fail[500010];
int cnt[500010];
char s[1000010];
void ins(char s[]){
int p=0,len=strlen(s);
for(int i=0;i<len;i++){
if(!trie[p][s[i]-'a'])trie[p][s[i]-'a']=++num;
p=trie[p][s[i]-'a'];
}cnt[p]++;
}
queue<int>q;
void build(){
for(int i=0;i<26;i++)if(trie[0][i])q.push(trie[0][i]);
while(!q.empty()){
int u=q.front();q.pop();
for(int i=0;i<26;i++){
if(trie[u][i]){//存在这个节点 更新fail
fail[trie[u][i]]=trie[fail[u]][i];
q.push(trie[u][i]);
}
else trie[u][i]=trie[fail[u]][i];//不存在则修改trie使得子节点指向之前的失配指针
}
}
}
int query(char s[]){
int p=0,ans=0,len=strlen(s);
for(int i=0;i<len;i++){
p=trie[p][s[i]-'a'];
for(int j=p;j&&cnt[j]!=-1;j=fail[j]){//暴力跳所有的fail指针
ans+=cnt[j];cnt[j]=-1;
}
}
return ans;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%s",s);ins(s);
}build();
scanf("%s",s);
printf("%d",query(s));
return 0;
}
然后那个加强版的其实也可以过了,就是小修小补一下。记录文本串跳
然而这样是过不去那个二次加强版的。这时候就要用到之前提到的
void query(char s[]){
int p=0,len=strlen(s);
for(int i=0;i<len;i++){
p=trie[p][s[i]-'a'];
ans[p]++;
}
}
void tuopu(){
for(int i=1;i<=num;i++){
if(ind[i]==0)q.push(i);
}
while(!q.empty()){
int u=q.front();q.pop();
cnt[vis[u]]=ans[u];
ind[fail[u]]--;
ans[fail[u]]+=ans[u];
if(ind[fail[u]]==0)q.push(fail[u]);
}
}
来点简单应用。
P5231 玄武密码
题意:给你一堆模式串和一个文本串,求每个模式串在文本串上匹配的最长前缀。
直接把所有模式串扔进AC自动机,然后跑一遍文本串的匹配并记录能跳到的所有节点,最后在字典树上查询一遍所有模式串即可。
P2444 病毒
题意:给一堆
void build(){
for(int i=0;i<2;i++){
if(trie[0][i])q.push(trie[0][i]);
}
while(!q.empty()){
int u=q.front();q.pop();
for(int i=0;i<2;i++){
if(trie[u][i]){
fail[trie[u][i]]=trie[fail[u]][i];
cnt[trie[u][i]]|=cnt[fail[trie[u][i]]];//就这一句 cnt是是否被标记
//因为如果fail被标记那u一定被标记
q.push(trie[u][i]);
}
else trie[u][i]=trie[fail[u]][i];
}
}
}
然后记录答案的时候直接在
void dfs(int x){
if(v[x]){
jud=true;return;
}
if(cnt[x])return;
v[x]=cnt[x]=true;
if(trie[x][0]&&!cnt[trie[x][0]])dfs(trie[x][0]);
if(trie[x][1]&&!cnt[trie[x][1]])dfs(trie[x][1]);
v[x]=false;
}
P2414 阿狸的打字机
题意:给若干模式串,多次询问,每次询问求第
根据我们
然后是另一个大头:
这个一般有一个套路:继承状态然后设
P4052 文本生成器
题意:给定若干模式串,求有多少
简易容斥一下,变成所有的(
意义显然,如果没有被标记就可以添加一个特定字符到下一个状态。只上
for(int i=1;i<=m;i++){
for(int j=0;j<=cnt;j++){
if(!vis[j]){
for(int k=0;k<26;k++){
if(!vis[trie[j][k]])dp[i][trie[j][k]]=(dp[i][trie[j][k]]+dp[i-1][j])%mod;
}
}
}
}
P4025 密码
题意:懒得说了。只讲解
看数据范围一眼状压。所以设
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】