字符串 ac自动姬

前言

省选临近,不能再颓了! 说着开始研究起moonlight串流。真香

本期博客之所以在csdn上发了一份,因为没有图床!如果有图床我一定会自力更生的!

好像和字符串没有毛关系

总之,为了备考省选,特地温习了一下ac自动姬

介绍

ac自动姬是一种多模匹配算法。说的直白一点,就是kmp的升级版,同时进行多个kmp。

说是多个kmp,其实它更多的借鉴的是kmp的思想,而不是算法。不会kmp可能可以理解ac自动姬,就像是不会加法也有可能理解乘法(多个加法);但是像线段树和树链剖分就有着严格的先后顺序,不可能在没有掌握线段树时就能理解树链剖分。

  1. trie树为必备知识,不懂trie树的童鞋请先学习trie树!(●ˇ∀ˇ●)
  2. kmp为非必备,学习完kmp再学习ac自动姬会更加深刻的理解

理论中的理论部分

对于多模匹配,我们肯定需要将模式串存储下来。怎么存?trie树呗

我们以一下模式串作为例子:

test

testt

est

好了,用trie树,我们把所有的模式串存了下来。

存完模式串,我们就开始匹配。随着主串的不断匹配,我们可以将匹配结果归纳如下:

  1. 假如当前匹配节点存在通向下一个节点的边,那么就转移

  2. 否则就是找不到下一个节点,那么就要按照失配来处理

这时就需要引入一种概念,“失配指针”。

什么是失配指针?失配指针就是当前点失配后,转移到的另外一个节点。(不太好理解,接着看下去)参照kmp理解一下失配指针

对象 kmp ac自动姬
失配指针 最长公共前后缀的坐标 trie树深度最深的合法坐标

还是不好理解?我们先把上述例子中的失配指针构建出来

照着图接着分析分析

参照kmp单模匹配。在kmp中,相当于只有左侧(\(root \Rightarrow t \Rightarrow e \Rightarrow s \Rightarrow t\))的这一条链。我们不断的找“最长公共前后缀”,是为了最大化利用我们已经匹配过的部分。

我们之所以要找“公共前后缀”,就是为了使得下一次匹配的对象是合法的。后缀与前缀重合,说明这段后缀可能作为下一个匹配的前缀被利用

而我们之所以要找“最长”的那一个,就是为了最大化的利用这个信息。假如当前公共前后缀存在不同的两个,且\(l_1<l_2\)。那么\(l_1\)长度的串一定包含在\(l_2\)长度的串中。如果\(l_2\)再次失配,它可以跳到\(l_1\)上,但是\(l_1\)失配,它却无法跳到\(l_2\)上

提供样例:ababa

“aba”与“a”分别是两个公共前后缀

“a”包含在“aba”中

好了,情况扩展到了ac自动姬多模匹配上。在多模匹配上就相当于提供了更多的可能来实现“最长公共前后缀”,一定要在保证“公共前后缀”的基础上尽可能“最长”,这样才能不浪费已经匹配出来的信息

理论中的代码部分

好了,基础的理论有了,我们就要开始实现。

回顾算法,主要难点分为两部分:构建fail指针匹配

构建fail指针

显然,我们在构建一个某一个节点的fail指针时,一定要事先求出来了所有深度小于这个节点的fail指针。换言之所有的fail只和深度小于这个点的节点有关联。

所有深度一致的先搜出来,保证深度按递增顺序搜出来...BFS!! 不难发现BFS就可以完美满足所有上述要求

我们取奔波最远的一个指针作为例子

在这个例子中,紫色的c为了获得fail指针,一共移动了两次

第一次:从c的父节点的fail指针开始寻找,发现第一个"b"下面并没有"c",接着转移

第二次:转移到了第二个b,发现这次,b下面接了一个c,那么就为这个c找到了失配指针

否则,假如到头了仍然没有找到,就将fail指针设为root即可

代码:

  1. void getfail(){
  2. //单独处理根节点
  3. fail[root]=-1;
  4. queue <int> line; line.push(root);
  5. while(!line.empty()){
  6. int u=line.front(); line.pop();
  7. for(int i=0;i<=25;++i){
  8. if(ch[u][i]){ //假如u节点后面跟着i节点
  9. line.push(ch[u][i]);
  10. int tmp=fail[u];
  11. //如果一下就到头了
  12. if(tmp==-1){
  13. fail[ch[u][i]]=root;
  14. }
  15. //否则尝试匹配
  16. else{
  17. while(tmp!=root&&!ch[tmp][i]{
  18. tmp=fail[tmp];
  19. }
  20. if(tmp!=root||ch[tmp][i]){
  21. fail[ch[u][i]]=ch[tmp][i];
  22. }
  23. else{
  24. fail[ch[u][i]]=root;
  25. last[ch[u][i]]=root;
  26. }
  27. }
  28. }
  29. }
  30. }
  31. }

匹配

好了,fail指针也构建完了,就可以开始匹配了

  1. 假如当前匹配节点存在通向下一个节点的边,那么就转移

  2. 否则就是找不到下一个节点,那么就要按照失配来处理

(搬过来~~)

我们知道怎么转移之后还有一个重要的问题:怎么统计?

从一个节点,通过fail指针遍历到的字串一定是该串的一个后缀,而鬼知道哪一个字串会被模式串匹配上,所以统计一定是随匹配转移时刻进行的。

而且,fail指针的转移的过程中,一定会遍历到所有被当前串包含的公共前后缀。因此可以保证正确性。

由于建trie树的时候记录了以某一点结尾的权值,故沿途加上所有权值即可。

代码如下:

  1. void kmp(string a){
  2. //转移部分
  3. int len=a.size();
  4. int u=root;
  5. for(int i=0;i<len;++i){
  6. int v=a[i]-'a';
  7. while(u!=root&&!ch[u][v]){
  8. u=fail[u];
  9. }
  10. if(u!=root||ch[u][v]) u=ch[u][v];
  11. else u=root;
  12. //统计部分
  13. int tmp=u;
  14. while(tmp!=root){
  15. rec[type[tmp]]+=dot[tmp];
  16. tmp=last[tmp];
  17. }
  18. }
  19. }

last优化

在转移过程中,会遇到很多权值为0(模式串中不包含),但fail指针却跳到了,导致fail指针要大量跳过一些对答案没有用的节点。

last优化就是要想法设法避免这件事。由于权值为0的fail指针是毫无意义的,那么就设last指针必定跳向一个权值不为0的指针

  1. 假如(fail指针所指向的点,其权值大于零)
  2. last指针就是fail指针
  3. 否则
  4. last指针就是fail指针指向的点的last指针

简单的实现:

  1. last[u]=dot[fail[u]]?fail[u]:last[fail[u]];

总结

至此,ac自动姬已经从理论到代码都有了完整的实现一举,参考代码如下:

该代码记录的是

  1. struct trie{
  2. static const int MAX=3e5+6;
  3. int cnt,tot,root;
  4. int ch[MAX][26],fail[MAX],last[MAX],dot[MAX];
  5. void clean(){
  6. cnt=0; tot=0; root=0;
  7. memset(ch,0,sizeof(ch));
  8. memset(fail,0,sizeof(fail));
  9. memset(last,0,sizeof(last));
  10. memset(dot,0,sizeof(dot));
  11. }
  12. void insert(string a){
  13. tot++;
  14. int len=a.size();
  15. int u=root;
  16. for(int i=0;i<len;++i){
  17. int v=a[i]-'a';
  18. if(!ch[u][v]){
  19. ch[u][v]=++cnt;
  20. }
  21. u=ch[u][v];
  22. }
  23. dot[u]++; type[u]=tot;
  24. }
  25. void getfail(){
  26. fail[root]=-1; last[root]=-1;
  27. queue <int> line; line.push(root);
  28. while(!line.empty()){
  29. int u=line.front(); line.pop();
  30. for(int i=0;i<=25;++i){
  31. if(ch[u][i]){
  32. line.push(ch[u][i]);
  33. int tmp=fail[u];
  34. if(tmp==-1){
  35. fail[ch[u][i]]=root;
  36. last[ch[u][i]]=root;
  37. }
  38. else{
  39. while(tmp!=root&&!ch[tmp][i]){
  40. tmp=fail[tmp];
  41. }
  42. if(tmp!=root||ch[tmp][i]){
  43. fail[ch[u][i]]=ch[tmp][i];
  44. last[ch[u][i]]=dot[fail[ch[u][i]]]?fail[ch[u][i]]:last[fail[ch[u][i]]];
  45. }
  46. else{
  47. fail[ch[u][i]]=root;
  48. last[ch[u][i]]=root;
  49. }
  50. }
  51. }
  52. }
  53. }
  54. }
  55. int kmp(string a){
  56. int len=a.size(),ans=0;
  57. int u=root;
  58. for(int i=0;i<len;++i){
  59. int v=a[i]-'a';
  60. while(u!=root&&!ch[u][v]){
  61. u=fail[u];
  62. }
  63. if(u!=root||ch[u][v]) u=ch[u][v];
  64. else u=root;
  65. int tmp=u;
  66. while(tmp!=root){
  67. ans+=dot[tmp]; dot[tmp]=0;
  68. tmp=last[tmp];
  69. }
  70. }
  71. return ans;
  72. }
  73. void print(int u){
  74. printf("%d %d\n",u,fail[u]);
  75. for(int i=0;i<=25;++i){
  76. if(ch[u][i]) print(ch[u][i]);
  77. }
  78. }
  79. }tree;

后记

深夜里又打了6e3个字..(>人<;)

希望通过这篇博客,能为自己和所有看到的人提供一点思路(´▽`ʃ♡ƪ)

ac自动姬的更多相关文章

  1. C#利用POST实现杭电oj的AC自动机器人,AC率高达50%~~

    暑假集训虽然很快乐,偶尔也会比较枯燥,,这个时候就需要自娱自乐... 然后看hdu的排行榜发现,除了一些是虚拟测评机的账号以外,有几个都是AC自动机器人 然后发现有一位作者是用网页填表然后按钮模拟,, ...

  2. URAL 1158 AC自动机上的简单DP+大数

    题目大意 在一种语言中的字母表中有N(N<=50)个字母,每个单词都由M(M<=50)个字母构成,因此,一共可以形成N^M个单词.但是有P(P<=10)个串是被禁止的,也就是说,任何 ...

  3. hdu 3247 AC自动+状压dp+bfs处理

    Resource Archiver Time Limit: 20000/10000 MS (Java/Others)    Memory Limit: 100000/100000 K (Java/Ot ...

  4. hdu 2243 考研路茫茫——单词情结(AC自动+矩阵)

    考研路茫茫——单词情结 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total ...

  5. 【洛谷4045】[JSOI2009] 密码(状压+AC自动机上DP)

    点此看题面 大致题意: 给你\(n\)个字符串,问你有多少个长度为\(L\)的字符串,使得这些字符串都是它的子串.若个数不大于\(42\),按字典序输出所有方案. 状压 显然,由于\(n\)很小,我们 ...

  6. POJ 3691 AC自动机上的dp

    题目大意: 给定一些不合理的DNA序列,再给一段较长的dna序列,问最少修改几次可以使序列中不存在任何不合理序列,不能找到修改方法输出-1 这里你修改某一个点的DNA可能会影响后面,我们不能单纯的找匹 ...

  7. HNU 13108-Just Another Knapsack Problem (ac自动机上的dp)

    题意: 给你一个母串,多个模式串及其价值,求用模式串拼接成母串(不重叠不遗漏),能获得的最大价值. 分析: ac自动机中,在字典树上查找时,用dp,dp[i]拼成母串以i为结尾的子串,获得的最大价值, ...

  8. POJ 1204 Word Puzzles | AC 自动鸡

    题目: 给一个字母矩阵和几个模式串,矩阵中的字符串可以有8个方向 输出每个模式串开头在矩阵中出现的坐标和这个串的方向 题解: 我们可以把模式串搞成AC自动机,然后枚举矩阵最外围一层的每个字母,向八个方 ...

  9. bzoj [Sdoi2014]数数 AC自动机上dp

    [Sdoi2014]数数 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 1264  Solved: 636[Submit][Status][Discu ...

  10. 【BZOJ1030】[JSOI2007] 文本生成器(AC自动机上跑DP)

    点此看题面 大致题意: 给你\(N\)个字符串(只含大写字母),要你求出有多少个由\(M\)个大写字母构成的字符串含有这\(N\)个字符串中的至少一个. \(AC\)自动机 看到题目,应该比较容易想到 ...

随机推荐

  1. (数据科学学习手札146)geopandas中拓扑非法问题的发现、诊断与修复

    本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 大家好我是费老师,geopandas作为在Pyt ...

  2. javaWEB中的四种域对象

    javaWEB中的四种域对象 (1)ServletContext ServletContext是最大的Web域对象,在整个工程内有效,可以存储一些需要全局部署的配置文件,也可以存储其他信息,不过因为它 ...

  3. Substring 在BCL和CLR里面搞了啥

    楔子 还是做点事情,不要那么散漫. 本文以简单的Substring(int startindex,int Length)函数为例,来递进下它在托管和非托管的一些行为. 以下均为个人理解,如有疏漏请指正 ...

  4. Linux操作系统,笔录!

    1.Linux 1.1.Linux介绍: Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户.多任务.支持多线程和多CPU的操作系统.它能运行主要的UNIX ...

  5. 抓包整理————ip 协议四[十五]

    前言 简单了解一下ip 协议选路问题. 正文 比如host1 到 host2是直接传输,因为host1和 host2 是同一交换机,直接arp表知道对方的mac. 第二种是他们是不同网段之间通信. 第 ...

  6. 用最少的代码模拟gRPC四种消息交换模式

    我们知道,建立在HTTP2/3之上的gRPC具有四种基本的通信模式或者消息交换模式(MEP: Message Exchange Pattern),即Unary.Server Stream.Client ...

  7. Java开发学习(四十二)----MyBatisPlus查询语句之条件查询

    一.条件查询的类 MyBatisPlus将书写复杂的SQL查询条件进行了封装,使用编程的形式完成查询条件的组合. 这个我们在前面都有见过,比如查询所有和分页查询的时候,都有看到过一个Wrapper类, ...

  8. Halo 主题 Redemption 首发版

    Redemption 一款专注阅读.写作的 Halo 博客主题.主要设计思想即是专注阅读.写作,是一款极简类型的博客主题. Redemption 部分设计灵感借鉴 Halo 博客 Zozo 主题,感谢 ...

  9. 【kafka】connect的timestamp模式无法同一秒插入多条记录问题解决

    一.现在问题 同时插入多条时间戳相同的记录 INSERT INTO "ABANK" VALUES ('1', 'CH', '00211', 'UBS Switzerland AG' ...

  10. 2.5:Python常用内置数据结构、多维数组ndarray、Series和DataFrame

    一.Python内置数据结构 1.赋值生成列表 la=[1,2,3,4] la 2.强制转换为列表 lb=list("Hello") lb 3.推导式生成列表 s="ab ...