学习笔记:AC自动机

话说AC自动机有什么用......我想要自动AC机

AC自动机简介: 

首先简要介绍一下AC自动机:Aho-Corasick automation,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法之一。一个常见的例子就是给出n个单词,再给出一段包含m个字符的文 章,让你找出有多少个单词在文章里出现过。要搞懂AC自动机,先得有字典树Trie和KMP模式匹配算法的基础知识。KMP算法是单模式串的字符匹配算 法,AC自动机是多模式串的字符匹配算法。

AC自动机的构造:

1.构造一棵Trie,作为AC自动机的搜索数据结构。

2.构造fail指针,使当前字符失配时跳转到具有最长公共前后缀的字符继续匹配。如 同 KMP算法一样, AC自动机在匹配时如果当前字符匹配失败,那么利用fail指针进行跳转。由此可知如果跳转,跳转后的串的前缀,必为跳转前的模式串的后缀并且跳转的新位 置的深度(匹配字符个数)一定小于跳之前的节点。所以我们可以利用 bfs在 Trie上面进行 fail指针的求解。

3.扫描主串进行匹配。

AC自动机详讲:

我们给出5个单词,say,she,shr,he,her。给定字符串为yasherhs。问多少个单词在字符串中出现过。

一、Trie

首先我们需要建立一棵Trie。但是这棵Trie不是普通的Trie,而是带有一些特殊的性质。

首先会有3个重要的指针,分别为p, p->fail, temp。

1.指针p,指向当前匹配的字符。若p指向root,表示当前匹配的字符序列为空。(root是Trie入口,没有实际含义)。

2.指针p->fail,p的失败指针,指向与字符p相同的结点,若没有,则指向root。

3.指针temp,测试指针(自己命名的,容易理解!~),在建立fail指针时有寻找与p字符匹配的结点的作用,在扫描时作用最大,也最不好理解。

对于Trie树中的一个节点,对应一个序列s[1...m]。此时,p指向字符s[m]。若在下一个字符处失配,即p->next[s[m+1]] == NULL,则由失配指针跳到另一个节点(p->fail)处,该节点对应的序列为s[i...m]。若继续失配,则序列依次跳转直到序列为空或出现 匹配。在此过程中,p的值一直在变化,但是p对应节点的字符没有发生变化。在此过程中,我们观察可知,最终求得得序列s则为最长公共后缀。另外,由于这个 序列是从root开始到某一节点,则说明这个序列有可能是某些序列的前缀。

再次讨论p指针转移的意义。如果p指针在某一字符s[m+1]处失配(即p->next[s[m+1]] == NULL),则说明没有单词s[1...m+1]存在。此时,如果p的失配指针指向root,则说明当前序列的任意后缀不会是某个单词的前缀。如果p的失 配指针不指向root,则说明序列s[i...m]是某一单词的前缀,于是跳转到p的失配指针,以s[i...m]为前缀继续匹配s[m+1]。

对于已经得到的序列s[1...m],由于s[i...m]可能是某单词的后缀,s[1...j]可能是某单词的前缀,所以s[1...m]中可能会出现 单词。此时,p指向已匹配的字符,不能动。于是,令temp = p,然后依次测试s[1...m], s[i...m]是否是单词。

构造的Trie为:


二、构造失败指针

用BFS来构造失败指针,与KMP算法相似的思想。

首先,root入队,第1次循环时处理与root相连的字符,也就是各个单词的第一个字符h和s,因为第一个字符不匹配需要重新匹配,所以第一个字符都指 向root(root是Trie入口,没有实际含义)失败指针的指向对应下图中的(1),(2)两条虚线;第2次进入循环后,从队列中先弹出h,接下来p 指向h节点的fail指针指向的节点,也就是root;p=p->fail也就是p=NULL说明匹配序列为空,则把节点e的fail指针指向 root表示没有匹配序列,对应图-2中的(3),然后节点e进入队列;第3次循环时,弹出的第一个节点a的操作与上一步操作的节点e相同,把a的 fail指针指向root,对应图-2中的(4),并入队;第4次进入循环时,弹出节点h(图中左边那个),这时操作略有不同。由于 p->next[i]!=NULL(root有h这个儿子节点,图中右边那个),这样便把左边那个h节点的失败指针指向右边那个root的儿子节点 h,对应图-2中的(5),然后h入队。以此类推:在循环结束后,所有的失败指针就是图-2中的这种形式。


三、扫描

构造好Trie和失败指针后,我们就可以对主串进行扫描了。这个过程和KMP算法很类似,但是也有一定的区别,主要是因为AC自动机处理的是多串模式,需要防止遗漏某个单词,所以引入temp指针。

匹配过程分两种情况:(1)当前字符匹配,表示从当前节点沿着树边有一条路径可以到达目标字符,此时只需沿该路径走向下一个节点继续匹配即可,目标 字符串指针移向下个字符继续匹配;(2)当前字符不匹配,则去当前节点失败指针所指向的字符继续匹配,匹配过程随着指针指向root结束。重复这2个过程 中的任意一个,直到模式串走到结尾为止。

 对照上图,看一下模式匹配这个详细的流程,其中模式串为yasherhs。对于i=0,1。Trie中没有对应的路径,故不做任何操 作;i=2,3,4时,指针p走到左下节点e。因为节点e的count信息为1,所以cnt+1,并且讲节点e的count值设置为-1,表示改单词已经 出现过了,防止重复计数,最后temp指向e节点的失败指针所指向的节点继续查找,以此类推,最后temp指向root,退出while循环,这个过程中 count增加了2。表示找到了2个单词she和he。当i=5时,程序进入第5行,p指向其失败指针的节点,也就是右边那个e节点,随后在第6行指向r 节点,r节点的count值为1,从而count+1,循环直到temp指向root为止。最后i=6,7时,找不到任何匹配,匹配过程结束。

到此,AC自动机入门知识就讲完了。HDU 2222入门题必须果断A掉。bzoj3172也要A。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cmath>
 5 #include<algorithm>
 6 #include<queue>
 7 #include<cstdlib>
 8 #include<iomanip>
 9 #include<cassert>
10 #include<climits>
11 #include<vector>
12 #include<list>
13 #define maxn 1000001
14 #define F(i,j,k) for(int i=j;i<=k;i++)
15 #define M(a,b) memset(a,b,sizeof(a))
16 #define FF(i,j,k) for(int i=j;i>=k;i--)
17 #define inf 0x7fffffff
18 #define maxm 2016
19 #define mod 1000000007
20 //#define LOCAL
21 using namespace std;
22 int read(){
23     int x=0,f=1;char ch=getchar();
24     while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
25     while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
26     return x*f;
27 }
28 int pos[maxn];
29 struct AC_automation
30 {
31     int cnt;
32     int next[maxn][26],sum[maxn],fail[maxn],q[maxn];
33     char ch[maxn];
34     AC_automation()
35     {
36         cnt=1;
37         F(i,0,25) next[0][i]=1;
38     }
39     void insert(int &pos)
40     {
41         int now=1;
42         cin>>ch;
43         int len=strlen(ch)-1;
44         F(i,0,len){
45             if(!next[now][ch[i]-'a']) next[now][ch[i]-'a']=++cnt;
46             now=next[now][ch[i]-'a'];
47             sum[now]++;
48         }
49         pos=now;
50     }
51     void build_fail()
52     {
53         int head=0,tail=1;
54         q[0]=1;
55         fail[1]=0;
56         while(head<tail)
57         {
58             int now=q[head];
59             head++;
60             F(i,0,25){
61                 int v=next[now][i];
62                 if(!v) continue;
63                 int k=fail[now];
64                 while(!next[k][i]) k=fail[k];
65                 fail[v]=next[k][i];
66                 q[tail++]=v;
67             }
68         }
69         FF(i,tail-1,0){
70             sum[fail[q[i]]]+=sum[q[i]];
71         }
72     }
73 }ac;
74 long long n,m;
75 int main()
76 {
77     std::ios::sync_with_stdio(false);//cout<<setiosflags(ios::fixed)<<setprecision(1)<<y;
78     #ifdef LOCAL
79     freopen("data.in","r",stdin);
80     freopen("data.out","w",stdout);
81     #endif
82     cin>>n;
83     F(i,1,n){
84         ac.insert(pos[i]);
85     }
86     ac.build_fail();
87     F(i,1,n){
88         cout<<ac.sum[pos[i]]<<endl;
89     }
90     return 0;
91 }
bzoj 3172

 

转载于:https://www.cnblogs.com/SBSOI/p/5681998.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/457586.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python闭包和装饰器

DAY 9. 闭包和装饰器 9.1 闭包 闭包就是内部函数对外部函数作用域内变量的引用 可以看出 闭包是针对函数的&#xff0c;还有两个函数&#xff0c;内部函数和外部函数闭包是为了让内部函数引用外部函数作用域内的变量的 我们先写两个函数 def fun1():print("我是fun1&q…

学历是铜牌,能力是银牌,人脉是金牌,思维是王牌

有人工作&#xff0c;有人上学&#xff0c;大家千万不要错过这篇文章&#xff0c;能看到这篇文章也是一种幸运&#xff0c;真的受益匪浅&#xff0c;对我有很大启迪&#xff0c;这篇文章将会改变你我的一生&#xff0c;真的太好了&#xff0c;希望与有缘人分享&#xff0c;也希…

石头剪刀布python编程_《python核心编程第二版》练习题——游戏:石头剪刀布

习题里比较有意思的一个题目&#xff0c;实现石头剪刀布这个游戏&#xff0c;起初设计的时候走弯路了(主要时被习题里那个“尽量少用if判断”给整晕了)&#xff0c;想的太复杂&#xff0c;后来发现其实非常简单&#xff0c;完全可以不写if语句。还是枚举法&#xff1a;#! /usr/…

SpringMvc面试题

f-sm-1. 讲下SpringMvc和Struts1,Struts2的比较的优势 性能上Struts1>SpringMvc>Struts2 开发速度上SpringMvc和Struts2差不多,比Struts1要高f-sm-2. 讲下SpringMvc的核心入口类是什么,Struts1,Struts2的分别是什么 SpringMvc的是DispatchServlet,Struts1的是ActionServl…

python 鸭子类型

DAY 10. 鸭子类型 这个概念来源于美国印第安纳州的诗人詹姆斯惠特科姆莱利&#xff08;James Whitcomb Riley,1849-1916&#xff09;的诗句&#xff1a;”When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.”…

thinkphp一句话疑难解决笔记

URL_PATHINFO_DEPR, depr表示 网页路径"分隔符",用"-", 有利于seo,注意是从 sername/index.php(开始的)/home-user-login-var-value开始的,pathinfo也支持普通的参数传值(仅仅支持参数...). 在thinkphp中,有两个地方使用depr,另一个就是tpl的文件目录组织分…

python选取特定行_pandas.DataFrame选取/排除特定行的方法

pandas.DataFrame选取特定行使用Python进行数据分析时&#xff0c;经常要使用到的一个数据结构就是pandas的DataFrame&#xff0c;如果我们想要像Excel的筛选那样&#xff0c;只要其中的一行或某几行&#xff0c;可以使用isin()方法&#xff0c;将需要的行的值以列表方式传入&a…

学校选址_洛谷U3451_带权中位数

题目描述 在一条大路一旁有许多栋楼&#xff0c;每栋楼里有许多小学生&#xff08;哈哈哈一波小学生来袭&#xff01;&#xff09;。但是这条路上没有小学&#xff01;&#xff01;&#xff01;&#xff01;所以唯恐世界不乱的牛A打算在路上&#xff08;汽车什么的都不敢来这个…

python 重载的实现(single-dispatch generic function)

DAY 11. python 重载 函数重载是指允许定义参数数量或类型不同的同名函数&#xff0c;程序在运行时会根据所传递的参数类型选择应该调用的函数 &#xff0c;但在默认情况下&#xff0c;python是不支持函数重载的&#xff0c;定义同名函数会发生覆盖 def foo(a:int):print(fin…

SQL中的多表查询,以及JOIN的顺序重要么?

说法是&#xff0c;一般来说&#xff0c;JOIN的顺序不重要&#xff0c;除非你要自己定制driving table。 示例&#xff1a; SELECT a.account_id, c.fed_id, e.fname, e.lname-> FROM account AS a INNER JOIN customer AS c-> ON a.cust_id c.cust_id-> INNER JOIN …

python可变对象 不可变对象_【Python】可变对象和不可变对象

在 Python 中一切都可以看作为对象。每个对象都有各自的 id, type 和 value。id: 当一个对象被创建后&#xff0c;它的 id 就不会在改变&#xff0c;这里的 id 其实就是对象在内存中的地址&#xff0c;可以使用 id() 去查看对象在内存中地址。type: 和 id 一样当对象呗创建之后…

MySQL 调优基础(三) Linux文件系统

Linux的文件系统有点像MySQL的存储引擎&#xff0c;它支持各种各样的文件系统。它最上层是通过 virtual files system虚拟文件系统作为一个抽象接口层来对外提供调用的。然后下层的各种文件系统实现这些调用接口就行了。 1. Linux 中的 日志文件系统和非日志文件系统 文件内容的…

python 经典类和新式类

DAY 12. python新式类和旧式类 继承自object基类的类叫做新式类&#xff0c;否则叫做旧式类&#xff0c;python3中的类默认是新式类&#xff0c;之前版本默认是旧式类 rootkail:~# python python 2.7.15 (default,Jul 28 2018,11:29:29) [GCC 8.1.0] on linux2 Type "he…

Why does pthread_cond_signal not work?【转】

转自&#xff1a;http://stackoverflow.com/questions/16819169/why-does-pthread-cond-signal-not-work# 0 down vote favorite I am currently learing all around POSIX threads (pthread). I now have created a simple program which increased a shared value by 7 until…

Android开发技术周报 Issue#72

新闻 Android N 最初预览版&#xff1a;开发者 API 和工具教程 Gradle依赖的统一管理 理解Java垃圾回收机制 浅谈 Android 编程思想和架构 由Android 65K方法数限制引发的思考 Android音频开发&#xff08;1&#xff09;&#xff1a;基础知识 Android音频开发&#xff08;…

python 单例模式的四种实现方法

DAY 13. 单例设计 13.1 什么是单例设计 一个类每次实例化返回的都是同一个对象&#xff0c;这种设计模式叫做单例设计&#xff0c;这个类叫做单例类 13.2 实现单例设计的方法 13.2.1 重写__new__() class Foo:def __new__(cls,*args, **kwargs):# 如果是第一次实例化&…

Redis3.2.5部署(单节点)

1.安装jdk1.8 [rootsht-logstash-01 ~]# cd /usr/java/ [rootsht-logstash-01 java]# wget --no-check-certificate --no-cookies --header "Cookie: oraclelicenseaccept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/8u111-b14/jdk-8u111…

字节跳动 设计模式 pdf_凭这份pdf我拿下了美团、字节跳动、阿里、小米等大厂的offer...

关于程序员&#xff0c;除了做项目来提高自身的技术之外&#xff0c;还有一种提升自己的专业技能就是&#xff1a;多&#xff01;看&#xff01;书&#xff01;小编整理出一篇Java进阶架构师之路的核心知识&#xff0c;同时也是面试时面试官必问的知识点&#xff0c;篇章也是包…

B. One Bomb (#363 Div.2)

B. One Bombtime limit per test1 secondmemory limit per test256 megabytesinputstandard inputoutputstandard outputYou are given a description of a depot. It is a rectangular checkered field of n  m size. Each cell in a field can be empty (".") or…

力扣交替打印FooBar

这道题要注意的是两个线程唤醒和等待的顺序&#xff0c;应为第一个线程会比第二个线程更早结束&#xff0c;所以如果第一个线程已经结束&#xff0c;而第二个线程还在等待被唤醒&#xff0c;那第二个线程会一直等待下去&#xff0c;因此第一个线程要先等待后唤醒&#xff0c;这…