AC自动机学习小结
AC自动机
简要说明
- \(AC\) 自动机,全称 \(Aho-Corasick\ automaton\) ,是一种有限状态自动机,应用于多模式串匹配.在 \(OI\) 中通常搭配 \(dp\) 食用.因为它是状态自动机.
- 感性理解:在 \(Trie\) 树上加上 \(fail\) 指针.具体的讲解可以去看dalao们的博客(因为我实在是太菜了讲不好).
题目
Keywords Search
- 题目:给若干个模式串,再给一个文本串,问有几个模式串在文本串中出现过.
- 板子题.注意一个模式串只被计算一次,统计后做上标记.
- 这里采用的是补全 \(Trie\) 树的写法.
#include"bits/stdc++.h"
using namespace std;
typedef long long LoveLive;
inline int read()
{
int out=0,fh=1;
char jp=getchar();
while ((jp>'9'||jp<'0')&&jp!='-')
jp=getchar();
if (jp=='-')
{
fh=-1;
jp=getchar();
}
while (jp>='0'&&jp<='9')
{
out=out*10+jp-'0';
jp=getchar();
}
return out*fh;
}
const int MAXN=5e5+10;
const int Siz=26;
struct AhoCorasick{
int idx;
int ch[MAXN][Siz];
int fail[MAXN];
int val[MAXN];
void init()
{
idx=0;
memset(val,0,sizeof val);
memset(ch,0,sizeof ch);
memset(fail,0,sizeof fail);
}
AhoCorasick()
{
init();
}
void ins(char s[],int n)
{
int u=0;
for(int i=0;i<n;++i)
{
int k=s[i]-'a';
if(!ch[u][k])
ch[u][k]=++idx;
u=ch[u][k];
}
++val[u];
}
void getfail()
{
queue<int> q;
for(int i=0;i<Siz;++i)
if(ch[0][i])
q.push(ch[0][i]);
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=0;i<Siz;++i)
{
if(ch[u][i])
{
fail[ch[u][i]]=ch[fail[u]][i];
q.push(ch[u][i]);
}
else
ch[u][i]=ch[fail[u]][i];
}
}
}
int query(char s[],int n)
{
int u=0,res=0;
for(int i=0;i<n;++i)
{
int k=s[i]-'a';
u=ch[u][k];
for(int j=u;j && val[j]!=-1;j=fail[j])
res+=val[j],val[j]=-1;
}
return res;
}
}ac;
char buf[MAXN<<1];
int main()
{
int T=read();
while(T--)
{
ac.init();
int n=read();
for(int i=1;i<=n;++i)
{
scanf("%s",buf);
ac.ins(buf,strlen(buf));
}
ac.getfail();
scanf("%s",buf);
int ans=ac.query(buf,strlen(buf));
printf("%d\n",ans);
}
return 0;
}
玄武密码
- 题意:给若干模式串和一个文本串.求每个模式串在文本串上能匹配的最大前缀长度.
- 将模式串建成一个 \(AC\) 自动机,匹配文本串的时候往前暴力跳,跳到第一个合法的位置即可.
#include"bits/stdc++.h"
using namespace std;
typedef long long LoveLive;
inline int read()
{
int out=0,fh=1;
char jp=getchar();
while ((jp>'9'||jp<'0')&&jp!='-')
jp=getchar();
if (jp=='-')
{
fh=-1;
jp=getchar();
}
while (jp>='0'&&jp<='9')
{
out=out*10+jp-'0';
jp=getchar();
}
return out*fh;
}
const int MAXN=1e7+10;
const int Siz=4;
int n,m;
int len[MAXN];
struct AhoCorasick{
int idx;
inline int id(char x)
{
if(x=='E')
return 0;
if(x=='W')
return 1;
if(x=='N')
return 2;
return 3;
}
int ch[MAXN][Siz];
int fail[MAXN];
int marked[MAXN];
int f[MAXN];
int val[MAXN];
AhoCorasick()
{
idx=0;
memset(fail,0,sizeof fail);
memset(ch,0,sizeof ch);
memset(marked,0,sizeof marked);
memset(val,0,sizeof val);
}
void ins(char s[],int v)
{
int u=0;
for(int i=0;i<len[v];++i)
{
int k=id(s[i]);
if(!ch[u][k])
{
f[++idx]=u;
ch[u][k]=idx;
}
u=ch[u][k];
}
val[v]=u;
}
void getfail()
{
queue<int> q;
for(int i=0;i<Siz;++i)
if(ch[0][i])
q.push(ch[0][i]);
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=0;i<Siz;++i)
{
if(ch[u][i])
{
fail[ch[u][i]]=ch[fail[u]][i];
q.push(ch[u][i]);
}
else
ch[u][i]=ch[fail[u]][i];
}
}
}
void mark(char *s)
{
int u=0;
for(int i=0;i<n;++i)
{
int k=id(s[i]);
u=ch[u][k];
for(int j=u;j;j=fail[j])
{
if(marked[j])
break;
marked[j]=1;
}
}
}
int work(int x)
{
int ans=len[x];
for(int i=val[x];i;i=f[i],ans--)
if(marked[i])
return ans;
}
}ac;
char buf[110];
char s[MAXN];
int main()
{
n=read(),m=read();
scanf("%s",s);
for(int i=1;i<=m;++i)
{
scanf("%s",buf);
len[i]=strlen(buf);
ac.ins(buf,i);
}
ac.getfail();
ac.mark(s);
for(int i=1;i<=m;++i)
{
int ans=ac.work(i);
printf("%d\n",ans);
}
return 0;
}
Censoring
- 题意:给若干模式串和一个文本串.每次从文本串开头找到一个模式串,将其删去,直到无法删去为止,求出最后剩余的文本.保证任一个模式串中没有其他模式串.
- 将模式串建成一个 \(AC\) 自动机,用一个栈来维护当前剩余的字符.匹配成功时直接修改栈顶.同时需要维护每个节点在 \(Trie\) 树上的位置.这样删除后可以立即得出当前的位置 \(u\) .
#include"bits/stdc++.h"
using namespace std;
typedef long long LoveLive;
inline int read()
{
int out=0,fh=1;
char jp=getchar();
while ((jp>'9'||jp<'0')&&jp!='-')
jp=getchar();
if (jp=='-')
{
fh=-1;
jp=getchar();
}
while (jp>='0'&&jp<='9')
{
out=out*10+jp-'0';
jp=getchar();
}
return out*fh;
}
const int MAXN=1e5+10;
const int Siz=26;
int len[MAXN];
struct AhoCorasick{
int idx;
int ch[MAXN][Siz];
int fail[MAXN];
int val[MAXN];
AhoCorasick()
{
idx=0;
memset(ch,0,sizeof ch);
memset(fail,0,sizeof fail);
memset(val,0,sizeof val);
}
void ins(char *s,int n,int v)
{
int u=0;
for(int i=0;i<n;++i)
{
int k=s[i]-'a';
if(!ch[u][k])
ch[u][k]=++idx;
u=ch[u][k];
}
val[u]=v;
}
void getfail()
{
queue<int> q;
for(int i=0;i<Siz;++i)
if(ch[0][i])
q.push(ch[0][i]);
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=0;i<Siz;++i)
{
if(ch[u][i])
{
fail[ch[u][i]]=ch[fail[u]][i];
q.push(ch[u][i]);
}
else
ch[u][i]=ch[fail[u]][i];
}
}
}
void solve(char *s,int n)
{
int u=0;
char stk[MAXN];
int tp=0;
int lst[MAXN];
for(int i=0;i<n;++i)
{
int k=s[i]-'a';
stk[++tp]=s[i];
lst[tp]=ch[u][k];
if(val[lst[tp]])
tp-=val[lst[tp]];
u=lst[tp];
}
for(int i=1;i<=tp;++i)
printf("%c",stk[i]);
puts("");
}
}ac;
char buf[MAXN];
char s[MAXN];
int main()
{
scanf("%s",s);
int n=read();
for(int i=1;i<=n;++i)
{
scanf("%s",buf);
int len=strlen(buf);
ac.ins(buf,len,len);
}
ac.getfail();
ac.solve(s,strlen(s));
return 0;
}
单词
- 题意:给出一个由若干单词组成的单词表,问每个单词在这个表中出现了几次.
- 很像一个 \(kmp\) 或是 \(AC\) 自动机裸题,然而并没有那么简单.用自动机做 \(n\) 次匹配,可能会被卡掉.如果一直跳 \(fail\) 指针,就可以构造一组数据让你一直跳.
- 正确的做法是使用 \(fail\) 树,连出这样所有的有向边 \(fail[x]->x\) .自动机上,每个节点都代表了一个前缀,连出边后,可以发现父亲节点是儿子节点的后缀.
- 而一个字符串被匹配的次数恰好等于以它为后缀的前缀数目,即 \(fail\) 树中子树的大小.
- 插入字符串的时候将经过的每个节点的权值 \(+1\) ,最后 \(dfs\) 统计即可.
#include"bits/stdc++.h"
using namespace std;
typedef long long LoveLive;
inline int read()
{
int out=0,fh=1;
char jp=getchar();
while ((jp>'9'||jp<'0')&&jp!='-')
jp=getchar();
if (jp=='-')
{
fh=-1;
jp=getchar();
}
while (jp>='0'&&jp<='9')
{
out=out*10+jp-'0';
jp=getchar();
}
return out*fh;
}
const int MAXN=1e6+10;
const int Siz=26;
int n;
struct AhoCorasick{
int idx;
int ch[MAXN][Siz];
int fail[MAXN];
int pos[MAXN];
int val[MAXN];
int ecnt;
int head[MAXN],to[MAXN],nx[MAXN],siz[MAXN];
AhoCorasick()
{
idx=0;
ecnt=0;
memset(ch,0,sizeof ch);
memset(fail,0,sizeof fail);
memset(val,0,sizeof val);
memset(head,0,sizeof head);
}
void ins(char *s,int len,int v)
{
int u=0;
for(int i=0;i<len;++i)
{
int k=s[i]-'a';
if(!ch[u][k])
ch[u][k]=++idx;
u=ch[u][k];
++val[u];
}
pos[v]=u;
}
void addedge(int u,int v)
{
++ecnt;
nx[ecnt]=head[u];
to[ecnt]=v;
head[u]=ecnt;
}
void getfail()
{
queue<int> q;
for(int i=0;i<Siz;++i)
if(ch[0][i])
q.push(ch[0][i]);
while(!q.empty())
{
int u=q.front();
addedge(fail[u],u);
q.pop();
for(int i=0;i<Siz;++i)
{
if(ch[u][i])
{
fail[ch[u][i]]=ch[fail[u]][i];
q.push(ch[u][i]);
}
else
ch[u][i]=ch[fail[u]][i];
}
}
}
void dfs(int u)
{
siz[u]=val[u];
for(int i=head[u];i;i=nx[i])
{
int v=to[i];
dfs(v);
siz[u]+=siz[v];
}
}
void pr()
{
for(int i=1;i<=n;++i)
printf("%d\n",siz[pos[i]]);
}
}ac;
char buf[MAXN];
int main()
{
n=read();
for(int i=1;i<=n;++i)
{
scanf("%s",buf);
ac.ins(buf,strlen(buf),i);
}
ac.getfail();
ac.dfs(0);
ac.pr();
return 0;
}
病毒
- 题意:给出若干个 \(01\) 串,问是否存在一个无限长的 \(01\) 串,满足所有给出的串都不是它的子串.
- 将给出的串插入到 \(AC\) 自动机里,那么若存在一个环,环上的节点及它们沿 \(fail\) 指针向上跳都不经过单词末节点,则符合要求.
- 插入的时候将权值一起合并,最后做一次 \(dfs\) 即可.
#include"bits/stdc++.h"
using namespace std;
typedef long long LoveLive;
inline int read()
{
int out=0,fh=1;
char jp=getchar();
while ((jp>'9'||jp<'0')&&jp!='-')
jp=getchar();
if (jp=='-')
{
fh=-1;
jp=getchar();
}
while (jp>='0'&&jp<='9')
{
out=out*10+jp-'0';
jp=getchar();
}
return out*fh;
}
const int MAXN=3e4+10;
const int Siz=2;
struct AhoCorasick{
int idx;
int ch[MAXN][Siz];
int fail[MAXN];
int val[MAXN];
AhoCorasick()
{
idx=0;
memset(ch,0,sizeof ch);
memset(val,0,sizeof val);
memset(fail,0,sizeof fail);
}
void ins(char *s,int len)
{
int u=0;
for(int i=0;i<len;++i)
{
int k=s[i]-'0';
if(!ch[u][k])
ch[u][k]=++idx;
u=ch[u][k];
}
val[u]=1;
}
void getfail()
{
queue<int> q;
for(int i=0;i<Siz;++i)
if(ch[0][i])
q.push(ch[0][i]);
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=0;i<Siz;++i)
{
if(ch[u][i])
{
fail[ch[u][i]]=ch[fail[u]][i];
val[ch[u][i]]|=val[ch[fail[u]][i]];
q.push(ch[u][i]);
}
else
ch[u][i]=ch[fail[u]][i];
}
}
}
int vis[MAXN],inc[MAXN];
int dfs(int u)
{
inc[u]=1;
int v;
for(int i=0;i<Siz;++i)
{
v=ch[u][i];
if(inc[v])
return 1;
if(vis[v] || val[v])
continue;
vis[v]=1;
if(dfs(v))
return 1;
}
inc[u]=0;
return 0;
}
void solve()
{
memset(vis,0,sizeof vis);
memset(inc,0,sizeof inc);
if(dfs(0))
puts("TAK");
else
puts("NIE");
}
}ac;
int n;
char buf[MAXN];
int main()
{
n=read();
for(int i=1;i<=n;++i)
{
scanf("%s",buf);
ac.ins(buf,strlen(buf));
}
ac.getfail();
ac.solve();
return 0;
}
文本生成器
- 题意:给出若干个由大写字母构成的单词,问长度为 \(m\) ,由大写字母构成的字符串中,包含至少一个单词的数目.对 \(10007\) 取模.
- 可以先求出不包含任意一个单词的字符串数目,再用总数目\(26^m\)减去.
- 将单词建成一个 \(AC\) 自动机,类似上题,合并权值即可求出一个节点是否能被走到.
- 用 \(f[i][j]\) 表示已经走了 \(i\) 步,走到了节点 \(j\) 时的方案数. \(O(n^2)\ dp\) 即可.
#include"bits/stdc++.h"
using namespace std;
typedef long long LoveLive;
inline int read()
{
int out=0,fh=1;
char jp=getchar();
while ((jp>'9'||jp<'0')&&jp!='-')
jp=getchar();
if (jp=='-')
{
fh=-1;
jp=getchar();
}
while (jp>='0'&&jp<='9')
{
out=out*10+jp-'0';
jp=getchar();
}
return out*fh;
}
const int P=1e4+7;
inline int add(int a,int b)
{
return (a+b) % P;
}
inline int mul(int a,int b)
{
return a * b % P;
}
int fpow(int a,int b)
{
int res=1;
while(b)
{
if(b&1)
res=mul(a,res);
a=mul(a,a);
b>>=1;
}
return res;
}
const int MAXN=7777;
const int Siz=26;
int n,m;
struct AhoCorasick{
int idx;
int ch[MAXN][Siz];
int fail[MAXN];
int val[MAXN];
int f[101][MAXN];
AhoCorasick()
{
idx=0;
memset(ch,0,sizeof ch);
memset(fail,0,sizeof fail);
memset(val,0,sizeof val);
memset(f,-1,sizeof f);
}
void ins(char *s,int len)
{
int u=0;
for(int i=0;i<len;++i)
{
int k=s[i]-'A';
if(!ch[u][k])
ch[u][k]=++idx;
u=ch[u][k];
}
val[u]=1;
}
void getfail()
{
queue<int> q;
for(int i=0;i<Siz;++i)
if(ch[0][i])
q.push(ch[0][i]);
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=0;i<Siz;++i)
{
if(ch[u][i])
{
fail[ch[u][i]]=ch[fail[u]][i];
//val[ch[u][i]]|=fail[ch[u][i]];
q.push(ch[u][i]);
}
else
ch[u][i]=ch[fail[u]][i];
}
val[u]|=val[fail[u]];//注意.
}
}
int dfs(int i,int j)//已经走了i步,在节点j
{
if(f[i][j]!=-1)
return f[i][j];
if(val[j])
return 0;
if(i==m)
return 1;
int &res=f[i][j];
res=0;
for(int k=0;k<Siz;++k)
{
res=add(res,dfs(i+1,ch[j][k]));
}
return res;
}
void solve()
{
int ans=fpow(26,m);
ans=add(ans,P-dfs(0,0));
printf("%d\n",ans);
}
}ac;
char buf[MAXN];
int main()
{
n=read(),m=read();
for(int i=1;i<=n;++i)
{
scanf("%s",buf);
ac.ins(buf,strlen(buf));
}
ac.getfail();
ac.solve();
return 0;
}
小结
- \(AC\) 自动机可以解决一部分多模式串有关问题.其附带品 \(fail\) 树也有不错的性质.
AC自动机学习小结的更多相关文章
- AC自动机学习
今天包括这一周开始学习AC自动机了,有点晚,但我感觉努努力还来得及.4月份还得认认真真攻图论,加油! 为2个月后的邀请赛及省赛.东北赛做准备. 推荐AC自动机学习地址:http://www.cppbl ...
- AC自动机学习笔记-2(Trie图&&last优化)
我是连月更都做不到的蒟蒻博主QwQ 考虑到我太菜了,考完noip就要退役了,所以我决定还是把博客的倒数第二篇博客给写了,也算是填了一个坑吧.(最后一篇?当然是悲怆のnoip退役记啦QAQ) 所以我们今 ...
- [AC自动机][学习笔记]
用途 AC自动机适用于一类用多个子串在模板串中匹配的字符串问题. 也就是说先给出一个模板串,然后给出一些子串.要求有多少个子串在这个模板串中出现过. KMP与trie树 其实AC自动机就是KMP与tr ...
- AC自动机学习笔记-1(怎么造一台AC自动机?)
月更博主又来送温暖啦QwQ 今天我们学习的算法是AC自动机.AC自动机是解决字符串多模匹配问题的利器,而且代码也十分好打=w= 在这一篇博客里,我将讲解AC自动机是什么,以及怎么构建一个最朴素的AC自 ...
- AC自动机板子题/AC自动机学习笔记!
想知道484每个萌新oier在最初知道AC自动机的时候都会理解为自动AC稽什么的,,,反正我记得我当初刚知道这个东西的时候,我以为是什么神仙东西,,,(好趴虽然确实是个对菜菜灵巧比较难理解的神仙知识点 ...
- AC 自动机学习笔记
虽然 NOIp 原地爆炸了,目前进入 AFO 状态,但感觉省选还是要冲一把,所以现在又来开始颓字符串辣 首先先复习一个很早很早就学过但忘记的算法--自动 AC AC自动机. AC 自动机能够在 \(\ ...
- AC自动机学习笔记
AC自动机 ----多个模板的字符串匹配 字典树Trie加上失配边构成 插入操作:ac.insert(p[i],i);构造失配函数:ac.getFail();计算文本串T中每个模板串的匹配数:ac.f ...
- AC自动机算法小结
AC自动机,可惜不能自动AC 转载:飘过的小牛 OIer55242 简介 Aho-Corasick automation 该算法在1975年产生于贝尔实验室,是著名的多模匹配算法之一.一个常见的例子就 ...
- 【AC自动机】【字符串】【字典树】AC自动机 学习笔记
blog:www.wjyyy.top AC自动机是一种毒瘤的方便的多模式串匹配算法.基于字典树,用到了类似KMP的思维. AC自动机与KMP不同的是,AC自动机可以同时匹配多个模式串, ...
随机推荐
- Gym - 100712H Bridges(边—双连通分量)
https://vjudge.net/problem/Gym-100712H 题意: 给出一个图,求添加一条边后最少的桥数量. 思路: 参考了ZSQ大神的题解http://blog.csdn.net/ ...
- python一个元素全为数的列表做差分
woc = [7, 5, 7, 3, 5, 1, 2] diff = [ wo[i]-wo[i+1] for i in range(len(wo)-1) ]
- 【转】TCP那些事(上,下)
TCP是一个巨复杂的协议,因为他要解决很多问题,而这些问题又带出了很多子问题和阴暗面.所以学习TCP本身是个比较痛苦的过程,但对于学习的过程却能让人有很多收获.关于TCP这个协议的细节,我还是推荐你去 ...
- 【转】爬取豆瓣电影top250提取电影分类进行数据分析
一.爬取网页,获取需要内容 我们今天要爬取的是豆瓣电影top250页面如下所示: 我们需要的是里面的电影分类,通过查看源代码观察可以分析出我们需要的东西.直接进入主题吧! 知道我们需要的内容在哪里了, ...
- 重新学习MySQL数据库7:详解MyIsam与InnoDB引擎的锁实现
重新学习Mysql数据库7:详解MyIsam与InnoDB引擎的锁实现 说到锁机制之前,先来看看Mysql的存储引擎,毕竟不同的引擎的锁机制也随着不同. 三类常见引擎: MyIsam :不支持事务,不 ...
- 二十四 Python分布式爬虫打造搜索引擎Scrapy精讲—爬虫和反爬的对抗过程以及策略—scrapy架构源码分析图
1.基本概念 2.反爬虫的目的 3.爬虫和反爬的对抗过程以及策略 scrapy架构源码分析图
- Ubuntu下压缩解压文件
一般来说ubuntu 下带有tar 命令,可以用来解压和压缩之用.但是我们经常要与win下用户打交道,所以要安装一些解压工具如:rar zip 等命令. 如果要需要用到zip工具那么可以: sudo ...
- 【Raspberry Pi】 小问题汇总
注: 此系列为自己之前所搭建网站内容. 目前入手树莓派2,将遇到的一些琐碎的问题记录在此. 1. 更改时区 查看日期命令:date 输入sudo dpkg-reconfigure tzdata后按提示 ...
- Highcharts 配置语法;Highcharts 配置选项详细说明
Highcharts 配置语法 本章节我们将为大家介绍使用 Highcharts 生成图表的一些配置. 第一步:创建 HTML 页面 创建一个 HTML 页面,引入 jQuery 和 Highchar ...
- js排序算法02——插入排序
插入排序的思路是我们默认数组的第一个元素是有序的,从第二个元素开始依次和前面的元素比较,如果前面的元素大,就将前面的元素往后移一位,如果前面的元素小,就把该元素放在前面元素的后面.其实就和我们玩扑克牌 ...