学习笔记:AC自动机
2016-07-18 17:12
155 查看
话说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为:
bzoj 3172
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为:
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<queue> #include<cstdlib> #include<iomanip> #include<cassert> #include<climits> #include<vector> #include<list> #define maxn 1000001 #define F(i,j,k) for(int i=j;i<=k;i++) #define M(a,b) memset(a,b,sizeof(a)) #define FF(i,j,k) for(int i=j;i>=k;i--) #define inf 0x7fffffff #define maxm 2016 #define mod 1000000007 //#define LOCAL using namespace std; int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int pos[maxn]; struct AC_automation { int cnt; int next[maxn][26],sum[maxn],fail[maxn],q[maxn]; char ch[maxn]; AC_automation() { cnt=1; F(i,0,25) next[0][i]=1; } void insert(int &pos) { int now=1; cin>>ch; int len=strlen(ch)-1; F(i,0,len){ if(!next[now][ch[i]-'a']) next[now][ch[i]-'a']=++cnt; now=next[now][ch[i]-'a']; sum[now]++; } pos=now; } void build_fail() { int head=0,tail=1; q[0]=1; fail[1]=0; while(head<tail) { int now=q[head]; head++; F(i,0,25){ int v=next[now][i]; if(!v) continue; int k=fail[now]; while(!next[k][i]) k=fail[k]; fail[v]=next[k][i]; q[tail++]=v; } } FF(i,tail-1,0){ sum[fail[q[i]]]+=sum[q[i]]; } } }ac; long long n,m; int main() { std::ios::sync_with_stdio(false);//cout<<setiosflags(ios::fixed)<<setprecision(1)<<y; #ifdef LOCAL freopen("data.in","r",stdin); freopen("data.out","w",stdout); #endif cin>>n; F(i,1,n){ ac.insert(pos[i]); } ac.build_fail(); F(i,1,n){ cout<<ac.sum[pos[i]]<<endl; } return 0; }
bzoj 3172
相关文章推荐
- js时间工具 MyTimeUtil.js
- 通过反射取得数组信息并修改数组内容
- 《linux学习》之root权限的使用及退出
- Cpp环境【Uva10815】【Vijos2785】安迪的第一本字典
- linux添加开机启动脚本
- 问题解决记录
- MapReduce扩展:应用程序如何运行于Hadoop Yarn之上
- 七天学会ASP.NET MVC (六)——线程问题、异常处理、自定义URL
- ubuntu-12.04.2忘记管理员密码(图解)
- libopcodes In function 'SUBWORDSISF': third-party/opcodes/cgen-ops.h:323: multiple definition
- 在CMOD上写ABAP取长文本
- 杭电 Problem 1715 大菲波数
- ARM9 mini2451裸机学习——UART驱动学习和整理
- c语言指针之排序两数大小
- 99乘法表
- 解决DevExpress控件在工具箱不显示的问题
- Linux(17)查看用户登陆历史的几个命令
- 【杭电oj】1222 - Wolf and Rabbit(GCD)
- android service
- 图像局部特征(十四)--MSER特征