• AC自动机


    AC自动机等于trie+kmp

    扩展:trie

    KMP

    暴力做法

    1. s为长串 p为小串
    2. for(int i=1;i<=s.size();i++)
    3. for(int j=1;j<=p.size();j++)
    4. if(s[i+j-1]!=p[j])
    5. break;
    6. if(j>m)
    7. 匹配成功

    next数组定义就是下一次最近的能成功的位置 在p串中以p[i]结尾的后缀 能够匹配的从1开始的非平凡前缀的最大长度(这里的p串是短串)

    步骤:

    1.next[0]=next[1]=0;

    2.

    1. //求next的过程
    2. for(int i=2,j=0;i<=n;i++)
    3. {
    4. while(j&&p[i]!=p[j+1]) j=ne[j];
    5. if(p[i]==p[j+1]) j++;
    6. ne[i]=j;
    7. }
    1. //kmp
    2. #include
    3. using namespace std;
    4. const int N=1e5+10,M=1e6+10;
    5. int n,m,ne[N];
    6. char p[N],s[M];
    7. int main()
    8. {
    9. ios_base::sync_with_stdio(0);//加速器
    10. cin.tie(0);
    11. cout.tie(0);
    12. cin>>n>>p+1>>m>>s+1;//读入数据
    13. //求next的过程
    14. for(int i=2,j=0;i<=n;i++)
    15. {
    16. while(j&&p[i]!=p[j+1]) j=ne[j];
    17. if(p[i]==p[j+1]) j++;
    18. ne[i]=j;
    19. }
    20. //kmp匹配过程
    21. for(int i=1,j=0;i<=m;i++)
    22. {
    23. while(j&&s[i]!=p[j+1]) j=ne[j];
    24. if(s[i]==p[j+1]) j++;
    25. if(j==n)//匹配成功
    26. {
    27. cout<' ';//输出答案
    28. j=ne[j];
    29. }
    30. }
    31. return 0;
    32. }

    AC自动机存的next是以这个点为结尾的所有后缀里面和某个最长前缀匹配 存的是最长前缀的结尾下标

     kmp对应到AC自动机

    1.搜索关键词

    信息学奥赛一本通(C++版)在线评测系统 

    1. #include
    2. using namespace std;
    3. const int N=10010,S=55,M=1000010;
    4. int n;
    5. int tr[N*S][26],cnt[N*S],idx;
    6. char str[M];
    7. int q[N*S],ne[N*S];
    8. void insert()//tire树
    9. {
    10. int p=0;//从头结点开始插
    11. for(int i=0;str[i];i++)
    12. {
    13. int t=str[i]-'a';//映射到数字
    14. if(!tr[p][t]) tr[p][t]=++idx;//假如这个位置没有这个字母,则新开辟个节点
    15. p=tr[p][t];//跳到这个位置
    16. }
    17. cnt[p]++;//这个位置的单词++
    18. }
    19. void build()//求next数组的过程
    20. {
    21. int hh=0,tt=-1;
    22. for(int i=0;i<26;i++)//把第一层的先入队
    23. if(tr[0][i])
    24. q[++tt]=tr[0][i];
    25. while(hh<=tt)
    26. {
    27. int t=q[hh++];//取出队头
    28. for(int i=0;i<26;i++)//枚举子节点
    29. {
    30. int c=tr[t][i];
    31. if(!c) continue;//假如这个位置没有这个节点,则直接跳
    32. int j=ne[t];
    33. while(j&&!tr[j][i]) j=ne[j];//假如匹配不成功则继续跳
    34. if(tr[j][i]) j=tr[j][i];//成功就为这个位置
    35. ne[c]=j;//这个节点的next更新一下
    36. q[++tt]=c;//把这个节点入队
    37. }
    38. }
    39. }
    40. int main()
    41. {
    42. int T;
    43. scanf("%d",&T);
    44. while(T--)
    45. {
    46. //清空上一层的状态
    47. memset(tr,0,sizeof tr);
    48. memset(cnt,0,sizeof cnt);
    49. memset(ne,0,sizeof ne);
    50. idx=0;
    51. scanf("%d",&n);
    52. for(int i=0;i
    53. {
    54. scanf("%s",str);
    55. insert();//插入这个单词
    56. }
    57. build();//求next数组
    58. scanf("%s",str);
    59. int res=0;
    60. for(int i=0,j=0;str[i];i++)//next匹配的过程
    61. {
    62. int t=str[i]-'a';
    63. while(j&&!tr[j][t]) j=ne[j];//假如这个位置匹配不上,则继续跳
    64. if(tr[j][t]) j=tr[j][t];//假如匹配了,则等于这个位置
    65. int p=j;
    66. while(p)//枚举p所重复的单词
    67. {
    68. res+=cnt[p];
    69. cnt[p]=0;//把这个位置的单词清空
    70. p=ne[p];//继续枚举上一个单词
    71. }
    72. }
    73. printf("%d\n",res);
    74. }
    75. return 0;
    76. }

    AC自动机优化成trie图 可以优化常数

    优化的位置是求next数组的位置跟next匹配的过程

    1. void build()//求next数组的过程
    2. {
    3. int hh=0,tt=-1;
    4. for(int i=0;i<26;i++)//把第一层的先入队
    5. if(tr[0][i])
    6. q[++tt]=tr[0][i];
    7. while(hh<=tt)
    8. {
    9. int t=q[hh++];//取出队头
    10. for(int i=0;i<26;i++)//枚举子节点
    11. {
    12. int p=tr[t][i];
    13. if(!p) tr[t][i]=tr[ne[t]][i];//假如这个节点不存在,直接就跳到上一层的节点
    14. else
    15. {
    16. ne[p]=tr[ne[t]][i];//让这个节点等于上一层的节点
    17. q[++tt]=p;//放进队列里
    18. }
    19. }
    20. }
    21. }
    22. for(int i=0,j=0;str[i];i++)//next匹配的过程
    23. {
    24. int t=str[i]-'a';
    25. j=tr[j][t];//因为优化的是直接等于next跳完后的值,所以直接跳到这个位置
    26. int p=j;
    27. while(p)//枚举p所重复的单词
    28. {
    29. res+=cnt[p];
    30. cnt[p]=0;//把这个位置的单词清空
    31. p=ne[p];//继续枚举上一个单词
    32. }
    33. }

    2.修复DNA(状态机+AC自动机)

    3691 -- DNA repair

    题意相当于给定我们一系列字符串,然后最后给我们一个长的字符串,问我们至少改变这个字符串的多少个字母使得这个长的字符串不包含前面输入的一系列字符串

     

    1. #include
    2. #include
    3. #define min(a,b) a>b?b:a
    4. const int N=1010;
    5. int n,m;
    6. int tr[N][4],dar[N],idx;//tr就是tire,dar就是标记这个单词是否存在
    7. char str[N];
    8. int q[N],ne[N];
    9. int f[N][N];//f就是状态机dp的f函数
    10. int get(char c)//把字母映射到数字里
    11. {
    12. if(c=='A') return 0;
    13. if(c=='T') return 1;
    14. if(c=='G') return 2;
    15. return 3;
    16. }
    17. void insert()//tire的插入
    18. {
    19. int p=0;
    20. for(int i=0;str[i];i++)
    21. {
    22. int t=get(str[i]);
    23. if(!tr[p][t]) tr[p][t]=++idx;
    24. p=tr[p][t];
    25. }
    26. dar[p]=1;//标记这个结尾的单词是存在的
    27. }
    28. void build()//求next数组
    29. {
    30. int hh=0,tt=-1;
    31. for(int i=0;i<4;i++)//把第一层放进队列中
    32. if(tr[0][i])
    33. q[++tt]=tr[0][i];
    34. while(hh<=tt)
    35. {
    36. int t=q[hh++];//求出队头
    37. for(int i=0;i<4;i++)//枚举下一层的节点
    38. {
    39. int p=tr[t][i];
    40. if(!p) tr[t][i]=tr[ne[t]][i];//假如这个节点不存在,则直接跳到存在的节点上
    41. else
    42. {
    43. ne[p]=tr[ne[t]][i];//存在则直接跳即可
    44. q[++tt]=p;
    45. dar[p]|=dar[ne[p]];//把这个单词标记为危险DNA序列
    46. }
    47. }
    48. }
    49. }
    50. int main()
    51. {
    52. int T=1;
    53. while(scanf("%d",&n),n)
    54. {
    55. //清空上一层的状态
    56. memset(tr,0,sizeof tr);
    57. memset(dar,0,sizeof dar);
    58. memset(ne,0,sizeof ne);
    59. idx=0;
    60. for(int i=0;i
    61. {
    62. scanf("%s",str);
    63. insert();//插入这个单词
    64. }
    65. build();//求next数组
    66. scanf("%s",str+1);
    67. m=strlen(str+1);
    68. memset(f,0x3f,sizeof f);//因为要求最小值,则初始化为正无穷
    69. f[0][0]=0;//从0个开始匹配到AC自动机第0个字母最小修改0次
    70. for(int i=0;i//枚举串的长度
    71. for(int j=0;j<=idx;j++)//枚举所有危险的单词
    72. for(int k=0;k<4;k++)//枚举危险单词的字母
    73. {
    74. int t=get(str[i+1])!=k;//看看这个单词是否等于第k个位置
    75. int p=tr[j][k];//获取危险单词的这个位置的字母
    76. if(!dar[p]) f[i+1][p]=min(f[i+1][p],f[i][j]+t);//假如不存在这个危险单词,则更新最小值
    77. }
    78. int res=0x3f3f3f3f;
    79. for(int i=0;i<=idx;i++) res=min(res,f[m][i]);//枚举所有需要更新的最小
    80. if(res==0x3f3f3f3f) res=-1;
    81. printf("Case %d: %d\n",T++,res);
    82. }
    83. return 0;
    84. }

    3.单词

    信息学奥赛一本通(C++版)在线评测系统

    相当于问其他子串里有多少个后缀与原串匹配。则反向递推求这个后缀匹配多少个前缀,相当于让f[i]出现的次数累加到f[next[i]]里面,按照拓扑序递推回去next[i]然后累加就行了

    1. #include
    2. using namespace std;
    3. const int N=1000010;
    4. int n;
    5. int tr[N][26],f[N],idx;//f记录的是以这个结尾的单词的数量
    6. int q[N],ne[N];
    7. char str[N];
    8. int id[210];
    9. void insert(int x)//tire的插入
    10. {
    11. int p=0;
    12. for(int i=0;str[i];i++)
    13. {
    14. int t=str[i]-'a';
    15. if(!tr[p][t]) tr[p][t]=++idx;
    16. p=tr[p][t];
    17. f[p]++;//因为记录的是前缀,则在内部就记录这个值了
    18. }
    19. id[x]=p;//
    20. }
    21. void build()
    22. {
    23. int hh=0,tt=-1;
    24. for(int i=0;i<26;i++)//把第一层的节点放进队列中
    25. if(tr[0][i])
    26. q[++tt]=tr[0][i];
    27. while(hh<=tt)
    28. {
    29. int t=q[hh++];
    30. for(int i=0;i<26;i++)//枚举可能的子节点
    31. {
    32. int p=tr[t][i];
    33. if(!p) tr[t][i]=tr[ne[t]][i];//假如不存在这个节点,则直接跳他的上一个节点
    34. else
    35. {
    36. ne[p]=tr[ne[t]][i];//直接跳上一层的节点
    37. q[++tt]=p;//放进队列里
    38. }
    39. }
    40. }
    41. }
    42. int main()
    43. {
    44. scanf("%d",&n);
    45. for(int i=0;i
    46. {
    47. scanf("%s",str);
    48. insert(i);//插入这个字符串
    49. }
    50. build();//求next数组
    51. for(int i=idx-1;i>=0;i--) f[ne[q[i]]]+=f[q[i]];//因为bfs搜索更新后的逆序就是拓扑序
    52. for(int i=0;iprintf("%d\n",f[id[i]]);
    53. return 0;
    54. }

  • 相关阅读:
    gin 快速入门手册
    分组后成员作为集合两两运算
    Taurus.MVC-Java 版本打包上传到Maven中央仓库(详细过程):2、PGP下载安装与密钥生成发布
    Docker 容器监控 - Weave Scope
    LeetCode 高频题目分类列表
    2009年408大题总结
    plsql连接oracle数据库
    Smart point智能指针(part.1)
    【论文阅读|基于 YOLO 的红外小目标检测的逆向范例】
    基于JavaSwing开发资产管理系统+报告 大作业 毕业设计项目源码
  • 原文地址:https://blog.csdn.net/lee_14141/article/details/127598103