AC自动机
AC自动机,全称Aho-Corasick自动机。如果没记错的话好像就是前缀自动机。
其实AC自动机就是KMP上树的产物。理解了KMP,那AC自动机应该也是很好理解的。
与KMP类似,AC自动机也是扔一个字符走一步。当前状态始终只有一个,每次如何走都是确定的,换句话说AC自动机是一种确定型有限状态自动机(DFA)。
进行模式匹配是AC自动机的基本应用。如果稍加拓展一下,就可以知道在AC自动机上走k步就相当于产生了一个长为k、只包含给定字符集的字符串。借助这个性质,可以在AC自动机上DP来解决一些字符串有关问题。
注意一般应用的时候都会把节点不存在的儿子指向跳fail之后的结果,这样就可以在匹配时省掉fail,代码简洁的同时还能提速。
来点例题:
1. COGS1913 AC自动机
一道AC自动机模板题,没啥好说的。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=;
void insert(const char*,int);
void getfail();
void match();
void inc(int);
int n,ch[maxn][]={{}},val[maxn]={},fail[maxn]={},last[maxn]={},q[maxn],head=,tail=,cnt=,a[];
char s[][],c;
int main(){
#define MINE
#ifdef MINE
freopen("ACautomata.in","r",stdin);
freopen("ACautomata.out","w",stdout);
#endif
scanf("%d",&n);
for(int i=;i<=n;i++){
scanf("%s",s[i]);
insert(s[i],i);
}
getfail();
match();
for(int i=;i<=n;i++)printf("%s %d\n",s[i],a[i]);
#ifndef MINE
printf("\n-------------------------DONE-------------------------\n");
for(;;);
#endif
return ;
}
void insert(const char *c,int i){
int x=;
while(*c){
if(!ch[x][*c-'a'])ch[x][*c-'a']=++cnt;
x=ch[x][*c-'a'];
c++;
}
val[x]=i;
}
void getfail(){
int x,y;
for(int c=;c<;c++)if(ch[][c])q[tail++]=ch[][c];
while(head!=tail){
x=q[head++];
for(int c=;c<;c++){
if(ch[x][c]){
q[tail++]=ch[x][c];
y=fail[x];
while(y&&!ch[y][c])y=fail[y];
fail[ch[x][c]]=ch[y][c];
last[ch[x][c]]=val[fail[ch[x][c]]]?fail[ch[x][c]]:last[fail[ch[x][c]]];
}
else ch[x][c]=ch[fail[x]][c];
}
}
}
void match(){
char c;
int x=;
for(;;){
do c=getchar();while((c<'a'||c>'z')&&c!=EOF);
if(c==EOF)return;
x=ch[x][c-'a'];
if(val[x])inc(x);
else if(last[x])inc(last[x]);
}
}
void inc(int x){
if(!x)return;
a[val[x]]++;
inc(last[x]);
}
2. POI2000 病毒
对模式串建AC自动机,问题就转化为了在AC自动机上是否存在一个不经过任何危险节点(单词节点或者存在last的节点)且无限长的路径。换句话说,就是问AC自动机上有没有环。
然后直接上dfs找环即可。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=;
void insert(const char*,int=);
void init();
void dfs(int);
int n,ch[maxn][]={{}},val[maxn]={},fail[maxn]={},last[maxn]={},q[maxn],cnt=,head=,tail=;
bool vis[maxn]={false},ins[maxn]={false},ok=false;
char c[maxn];
int main(){
#define MINE
#ifdef MINE
freopen("wir.in","r",stdin);
freopen("wir.out","w",stdout);
#endif
scanf("%d",&n);
for(int i=;i<n;i++){
scanf("%s",c);
insert(c);
}
init();
dfs();
if(!n||ok)printf("TAK");
else printf("NIE");
#ifndef MINE
printf("\n-------------------------DONE-------------------------\n");
for(;;);
#endif
return ;
}
void insert(const char* c,int x){
while(*c){
if(!ch[x][*c-''])ch[x][*c-'']=++cnt;
x=ch[x][*c-''];
c++;
}
val[x]++;
}
void init(){
int x,y;
for(int c=;c<;c++)if(ch[][c])q[tail++]=ch[][c];
while(head!=tail){
x=q[head++];
for(int c=;c<;c++){
if(ch[x][c]){
q[tail++]=ch[x][c];
y=fail[x];
while(y&&!ch[y][c])y=fail[y];
fail[ch[x][c]]=ch[y][c];
last[ch[x][c]]=val[ch[y][c]]?ch[y][c]:last[ch[y][c]];
}
else ch[x][c]=ch[fail[x]][c];
}
}
}
void dfs(int x){
if(val[x]||last[x])return;
if(ins[x]){
ok=true;
return;
}
if(vis[x])return;
vis[x]=ins[x]=true;
for(int c=;c<;c++){
if(ch[x][c]){
dfs(ch[x][c]);
if(ok)return;
}
}
ins[x]=false;
}
3. COGS2248 情书
问你是否n个模式串都在文本串中出现过,AC自动机模板题。
话说string瞎搞居然比AC自动机快……
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=;
void insert(const char*,int);
void getfail();
void match(const char*);
void inc(int);
int n,ch[maxn][]={{}},fail[maxn]={},last[maxn]={},val[maxn]={},q[maxn],head=,tail=,cnt=,a[];
char s[],c;
bool ok;
int main(){
#define MINE
#ifdef MINE
freopen("lettera.in","r",stdin);
freopen("lettera.out","w",stdout);
#endif
scanf("%d",&n);
for(int i=;i<=n;i++){
scanf("%*[^a-z]");
scanf("%[a-z]",s);
insert(s,i);
}
getfail();
for(;;){
scanf("%*[^a-z]");
if(scanf("%[a-z]",s)!=)break;
fill_n(a+,n,);
match(s);
ok=true;
for(int i=;i<=n;i++)if(!a[i]){
ok=false;
break;
}
printf(ok?"Yes\n":"No\n");
}
#ifndef MINE
printf("\n-------------------------DONE-------------------------\n");
for(;;);
#endif
return ;
}
void insert(const char *c,int i){
int x=;
while(*c){
if(!ch[x][*c-'a'])ch[x][*c-'a']=++cnt;
x=ch[x][*c-'a'];
c++;
}
val[x]=i;
}
void getfail(){
int x,y;
for(int c=;c<;c++)if(ch[][c])q[tail++]=ch[][c];
while(head!=tail){
x=q[head++];
for(int c=;c<;c++){
if(ch[x][c]){
q[tail++]=ch[x][c];
y=fail[x];
while(y&&!ch[y][c])y=fail[y];
fail[ch[x][c]]=ch[y][c];
last[ch[x][c]]=val[fail[ch[x][c]]]?fail[ch[x][c]]:last[fail[ch[x][c]]];
}
else ch[x][c]=ch[fail[x]][c];
}
}
}
void match(const char *c){
int x=;
while(*c&&*c!='$'){
x=ch[x][*c-'a'];
if(val[x])inc(x);
else if(last[x])inc(last[x]);
c++;
}
}
void inc(int x){
if(!x)return;
a[val[x]]++;
inc(last[x]);
}
其实例题还有很多,只不过限于时间(zi ji tai cai)没做,在此贴上大概思路:
1. [SCOI 2012] 喵星球上的点名
大概思路肯定是建AC自动机然后模式匹配。但是有一个问题,模式串太多,暴力匹配肯定会T。
考虑每个节点的last,显然它们构成了一棵树。因此可以对last树做树上差分,最后一遍dfs求出每个模式串出现的次数。至于每次有多少模式串出现,直接用节点的深度就行了。
2. 文本生成器(08年俞华程论文题)
感觉并不用减法原理就可以写……(不过不一定正确,如果有发现不对的话请告诉我)
先建AC自动机,然后就是一个AC自动机上的DP。
令f[i][j][0/1]为从i出发再走k步的方案数,第三维0表示没有出现过模式串,1表示出现过了。
转移方程:
f[i][j][0]=sum{f[ch[i][c]][j-1][0]}
f[i][j][1]=sum{f[ch[i][c]][j-1][1]+f[ch[i][c]][j-1][0]}
边界:
f[0][0][0]=1,其余j=0情况均为0。
然后,显然这是一个线性递推式,而状态又不多,因此可以用矩阵快速幂优化。构造一个转移矩阵,然后乘乘乘就好了。
尽头和开端,总有一个在等你。
AC自动机的更多相关文章
- 基于trie树做一个ac自动机
基于trie树做一个ac自动机 #!/usr/bin/python # -*- coding: utf-8 -*- class Node: def __init__(self): self.value ...
- AC自动机-算法详解
What's Aho-Corasick automaton? 一种多模式串匹配算法,该算法在1975年产生于贝尔实验室,是著名的多模式匹配算法之一. 简单的说,KMP用来在一篇文章中匹配一个模式串:但 ...
- python爬虫学习(11) —— 也写个AC自动机
0. 写在前面 本文记录了一个AC自动机的诞生! 之前看过有人用C++写过AC自动机,也有用C#写的,还有一个用nodejs写的.. C# 逆袭--自制日刷千题的AC自动机攻克HDU OJ HDU 自 ...
- BZOJ 2434: [Noi2011]阿狸的打字机 [AC自动机 Fail树 树状数组 DFS序]
2434: [Noi2011]阿狸的打字机 Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 2545 Solved: 1419[Submit][Sta ...
- BZOJ 3172: [Tjoi2013]单词 [AC自动机 Fail树]
3172: [Tjoi2013]单词 Time Limit: 10 Sec Memory Limit: 512 MBSubmit: 3198 Solved: 1532[Submit][Status ...
- BZOJ 1212: [HNOI2004]L语言 [AC自动机 DP]
1212: [HNOI2004]L语言 Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 1367 Solved: 598[Submit][Status ...
- [AC自动机]【学习笔记】
Keywords Search Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others)To ...
- AC自动机 HDU 3065
大概就是裸的AC自动机了 #include<stdio.h> #include<algorithm> #include<string.h> #include< ...
- AC自动机 HDU 2896
n个字串 m个母串 字串在母串中出现几次 #include<stdio.h> #include<algorithm> #include<string.h> #inc ...
- 【BZOJ-3881】Divljak AC自动机fail树 + 树链剖分+ 树状数组 + DFS序
3881: [Coci2015]Divljak Time Limit: 20 Sec Memory Limit: 768 MBSubmit: 508 Solved: 158[Submit][Sta ...
随机推荐
- BroadcastReceiver详解
详解 2014-08-20 19:42 13492人阅读 评论(8) 收藏 举报 分类: 5.andriod开发(148) 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] ...
- jmeter(三)Sample之SOAP/XML-RPC Request
项目背景:公司的微信端H5界面新开发了会员注册功能,需要对其进行压力测试 项目目标:需要承受每分钟最少6000的压力 一.建立一个测试计划(test plan) 之前有说过,jmeter打开后会自动生 ...
- 上海闪酷成为京东商城第一批独立软件开发商(ISV)
闪酷信息技术(上海)有限公司一直致力于为品牌企业提供电子商务软件及其服务,为其拓展电商渠道保驾护航.上海闪酷依据多年的行业经验和技术积累,与中国 最大的B2C商城达成战略合作,为其2万多家品牌供应商提 ...
- EasyUI datagrid : 启用行号、固定列及多级表头后,头部行号位置单元格错位的问题
症状如图: 上图中,行号列与checkbox 列融合了.解决方法是在datagrid 的 onLoadSuccess 事件中加入如下代码: var opts = $(this).datagrid('o ...
- JAVA中int、String的类型转换
int -> String int i=12345;String s="";第一种方法:s=i+"";第二种方法:s=String.valueOf(i); ...
- 给大家分享一个jQuery TAB插件演示
jquery tab选项卡插件示例页面代码,使用jquery.tabs.js轻量级的tab选项卡插件来实现,并支持鼠标滑过.点击.自动切换.数据回调等功能,有的是点击切换,有的是鼠标滑过切换,自带了多 ...
- 格雷码原理与Verilog实现
格雷码原理 格雷码是一个叫弗兰克*格雷的人在1953年发明的,最初用于通信.格雷码是一种循环二进制码或者叫作反射二进制码.格雷码的特点是从一个数变为相邻的一个数时,只有一个数据位发生跳变,由于这种特点 ...
- 最为简易的yii 教程(一)
了解目录的框架结构 framework主要有 base 框架核心组件 caching 缓存组件 db 数据库组件 gii ...
- bzoj2194: 快速傅立叶之二
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> #i ...
- svn清理失败且路径显示乱码
1.下载 sqlite数据库工具,sqlite3.exe下载地址:sqlite官网http://www.sqlite.org/download.html,我这里是windows操作系统,因此下载 Pr ...