题意 : 给出一些病毒串,问你由ATGC构成的长度为 n 且不包含这些病毒串的个数有多少个

分析 : 这题搞了我真特么久啊,首先你需要知道的前置技能包括 AC自动机、构建Trie图、矩阵快速幂,其中矩阵快速幂和AC自动机可能都熟悉,但是这题为什么和矩阵有关系?Trie图是什么呢?好像只听说过Trie树啊!下面我慢慢展开,首先声明本人水平实在实在有限,理解错误的地方请批评指证,万分感激!

与矩阵的联系( 你可能需要百度.... ) ==> 解决此题就要先了解到如何用矩阵去解决 求从A点到B点刚好经过K步的方案数( 可走重复点 ),在 Matrix67的博客里面就有说,并且HDU 2157就是一道原题,可以尝试去了解并且用快速幂AC它,总之最后的结论就是将整幅图转化为邻接矩阵,然后对矩阵求 k 次幂,最后矩阵的(A, B)点数值就是答案。这一题通过Trie图的转化,最后会变成一个很相似的问题,因此就能用矩阵优化解决。

如何转化?强烈推荐参考==>http://blog.csdn.net/morgan_xww/article/details/7834801

但是!这篇博客虽然解释的很棒,我一开始看完之后还是十分模糊,后来了解到这和普通的AC自动机是有区别的,上述博客当中构建出来的是Trie图,这和AC自动机的区别是啥呢?

              

何谓AC自动机保存后缀节点跳转?何谓”补边“?我是这么理解的,AC自动机在失配时要通过保存的 Fail 不断跳转来达到下一个合法状态,而Trie图是将所有的跳转信息存起来了,不用繁琐的跳,状态转移直接转即可(理解有误请指出!)先来看看这幅图的“效果”,构建此Trie图就是为了模拟构建长度为 n 的合法串的这个过程,想象一下当前构建到了....A 这样末尾为A的一个串,当前所处状态为 1 、那后面能再添什么字符呢?根据上图的“指示”,我们能够添C吗?显然不行,添C就会到达节点 2 这个不合法的状态,那 A 呢?显然是可以的,增添了A之后并没有转移到其他状态,合法!那G和T呢?当然也是可以的,这样会让状态转化到 0 节点。根据这样的规则,那么 n = 1 且 m = {"ACG"、"C"}的时候答案是 3,模拟一下看看就知道了!那么最后的答案就是从 0 这个初始节点走 K 步到达所有合法节点的方案和、用上面说到的那个矩阵问题里面的解决技巧就可以解决。这个实际上就是构建了一个状态图,每一个节点都拥有向“ATCG"转移的能力,也就是有四条出度,但是普通的AC自动机面对非法节点是无法用 Fail 来进行一步转移的,可能要回溯几步(俗称跳 Fail、找到一个最长前缀的节点和当前节点表示字符串的最长后缀一样、KMP思想),但是我们总是希望状态能够一步被转移,代表从一个状态到另一个状态步数+1,也就是什么意思呢?比如 1 这个节点的 A 出度这条边是不存在的,就需要我们去”补边“,很显然这条边应该是补向当前节点 Fail 节点指向的节点的 A 这条边,由于是自上而下 BFS 序更新,所以能够保证前面所有节点的 A 出边已经被计算出来,那么 1 的 Fail 指向的是 0 ,0 的A出边指向 1 ,所以 1 的  A 出边实际上还是指向自己,其他的也是类似。总的来说就是利用了原来AC自动机中不应该去更新的一些出边根据 Fail 补了上去,以达到方便进行状态转移,代码实现很简单,就是在原AC自动机BFS构建 Fail 指针的代码的时候对于不存在Next[i]的节点将其指向当前节点 Fail 节点指向Next[i],如果你看过我的AC自动机模板代码,那么会发现代码只是加多了一个语句就能达到这个效果,当然!我们还是需要添加一个标记来标记不合法节点的,值得注意的是,如果当前节点的 Fail 指向的节点是不合法节点的话,那么这个状态转移也是不允许的!

  1. inline void BuildFail(){
  2. Node[].fail = 0;
  3. que.push();
  4. while(!que.empty()){
  5. int top = que.front(); que.pop();
  6. ;///如果当前节点的Fail指针指向的节点也是末尾节点,那么这个节点也是不合法的!
  7.  
  8. ; i<Letter; i++){
  9. if(Node[top].Next[i]){
  10. ) Node[ Node[top].Next[i] ].fail = ;
  11. else{
  12. int v = Node[top].fail;
  13. ){
  14. if(Node[v].Next[i]){
  15. Node[ Node[top].Next[i] ].fail = Node[v].Next[i];
  16. break;
  17. }v = Node[v].fail;
  18. }) Node[ Node[top].Next[i] ].fail = ;
  19. }que.push(Node[top].Next[i]);
  20. }?Node[ Node[top].fail ].Next[i]:;///多了这一句!
  21. }
  22. }
  23. }

根据上述所说,实际上构建 Fail 和 ”补边“的代码还能更简便,如下

  1. //1) 如果son[i]不存在,将它指向 当前结点now的fail指针指
  2. //向结点的i号后继(保证一定已经计算出来)。
  3.  
  4. //2) 如果son[i]存在,将它的fail指针指向 当前结点now的fail
  5. //指针指向结点的i号后继(保证一定已经计算出来)。
  6. inline void BuildFail(){
  7. Node[].fail = 0;
  8. ; i<Letter; i++){
  9. ].Next[i]){
  10. Node[Node[].Next[i]].fail = ;
  11. que.push(Node[].Next[i]);
  12. }].Next[i] = ;///必定指向根节点
  13. }
  14. while(!que.empty()){
  15. int top = que.front(); que.pop();
  16. ;
  17. ; i<Letter; i++){
  18. int &v = Node[top].Next[i];
  19. if(v){
  20. que.push(v);
  21. Node[v].fail = Node[Node[top].fail].Next[i];
  22. }else v = Node[Node[top].fail].Next[i];
  23. }
  24. }
  25. }

那么只要我们绘出了这副 Trie 图,我们就能知道各个点到其他点只通过一步的方案数,最后存到矩阵去进行 n 次快速幂,最后累加从 0 到其他合法点的答案即可

如果不懂!没关系,那些都是我参考了很多东西得出来的口胡,可以看看原文,以上参考 ==>

http://www.doc88.com/p-9913363530128.html( AC自动机 与 Trie图 )

blog.csdn.net/mobius_strip/article/details/22549517 ( 大牛的总结 )

http://www.cppblog.com/menjitianya/archive/2014/07/10/207604.html ( AC自动机 与 Trie 图 )

最后AC代码(96ms)   提醒 : 如果TLE了,那么矩阵快速幂的过程中加完一行再模,不要边加边模

  1. #include<queue>
  2. #include<stdio.h>
  3. #include<string.h>
  4. using namespace std;
  5.  
  6. ;
  7. ;
  8. const int MOD = 1e5;
  9. int maxn;
  10. ];
  11.  
  12. ][]; }unit, M;
  13.  
  14. mat operator * (mat a, mat b)
  15. {
  16. mat ret;
  17. long long x;
  18. ; i<maxn; i++){
  19. ; j<maxn; j++){
  20. x = ;
  21. ; k<maxn; k++){
  22. x += (long long)a.m[i][k]*b.m[k][j];
  23. }
  24. ret.m[i][j] = x % MOD;
  25. }
  26. }
  27. return ret;
  28. }
  29.  
  30. inline ; i<maxn; i++) unit.m[i][i] = ; }
  31.  
  32. mat pow_mat(mat a, int n)
  33. {
  34. mat ret = unit;
  35. while(n){
  36. ) ret = ret * a;
  37. a = a*a;
  38. n >>= ;
  39. }
  40. return ret;
  41. }
  42.  
  43. struct Aho{
  44. struct StateTable{
  45. int Next[Letter];
  46. int fail, flag;
  47. }Node[Max_Tot];
  48. int Size;
  49. queue<int> que;
  50.  
  51. inline void init(){
  52. while(!que.empty()) que.pop();
  53. memset(Node[].Next, , ].Next));
  54. Node[].fail = Node[].flag = ;
  55. Size = ;
  56. }
  57.  
  58. inline void insert(char *s){
  59. ;
  60. ; s[i]; i++){
  61. int idx = mp[s[i]];
  62. if(!Node[now].Next[idx]){
  63. memset(Node[Size].Next, , sizeof(Node[Size].Next));
  64. Node[Size].fail = Node[Size].flag = ;
  65. Node[now].Next[idx] = Size++;
  66. }
  67. now = Node[now].Next[idx];
  68. }
  69. Node[now].flag = ;
  70. }
  71.  
  72. inline void BuildFail(){
  73. Node[].fail = 0;
  74. que.push();
  75. while(!que.empty()){
  76. int top = que.front(); que.pop();
  77. ;///如果当前节点的Fail指针指向的节点也是末尾节点,那么这个节点也是不合法的!
  78.  
  79. ; i<Letter; i++){
  80. if(Node[top].Next[i]){
  81. ) Node[ Node[top].Next[i] ].fail = ;
  82. else{
  83. int v = Node[top].fail;
  84. ){
  85. if(Node[v].Next[i]){
  86. Node[ Node[top].Next[i] ].fail = Node[v].Next[i];
  87. break;
  88. }v = Node[v].fail;
  89. }) Node[ Node[top].Next[i] ].fail = ;
  90. }que.push(Node[top].Next[i]);
  91. }?Node[ Node[top].fail ].Next[i]:;///多了这一句!
  92. }
  93. }
  94. }
  95. ////1) 如果son[i]不存在,将它指向 当前结点now的fail指针指
  96. ////向结点的i号后继(保证一定已经计算出来)。
  97. //
  98. ////2) 如果son[i]存在,将它的fail指针指向 当前结点now的fail
  99. ////指针指向结点的i号后继(保证一定已经计算出来)。
  100. // inline void BuildFail(){
  101. // Node[0].fail = 0;
  102. // for(int i=0; i<Letter; i++){
  103. // if(Node[0].Next[i]){
  104. // Node[Node[0].Next[i]].fail = 0;
  105. // que.push(Node[0].Next[i]);
  106. // }else Node[0].Next[i] = 0;///必定指向根节点
  107. // }
  108. // while(!que.empty()){
  109. // int top = que.front(); que.pop();
  110. // if(Node[Node[top].fail].flag) Node[top].flag = 1;
  111. // for(int i=0; i<Letter; i++){
  112. // int &v = Node[top].Next[i];
  113. // if(v){
  114. // que.push(v);
  115. // Node[v].fail = Node[Node[top].fail].Next[i];
  116. // }else v = Node[Node[top].fail].Next[i];
  117. // }
  118. // }
  119. // }
  120.  
  121. inline void BuildMatrix(){
  122. ; i<Size; i++)
  123. ; j<Size; j++)
  124. M.m[i][j] = ;
  125. ; i<Size; i++){
  126. ; j<Letter; j++){
  127. if(!Node[i].flag && !Node[ Node[i].Next[j] ].flag)
  128. M.m[i][Node[i].Next[j]]++;
  129. }
  130. }
  131. maxn = Size;
  132. }
  133.  
  134. }ac;
  135.  
  136. ];
  137. int main(void)
  138. {
  139. mp[,
  140. mp[,
  141. mp[,
  142. mp[;
  143. int n, m;
  144. while(~scanf("%d %d", &m, &n)){
  145. ac.init();
  146. ; i<m; i++){
  147. scanf("%s", S);
  148. ac.insert(S);
  149. }
  150. ac.BuildFail();
  151. ac.BuildMatrix();
  152. // for(int i=0; i<10; i++){
  153. // for(int j=0; j<10; j++){
  154. // printf("%d ", M.m[i][j]);
  155. // }puts("");
  156. // }puts("");
  157.  
  158. init_unit();
  159. M = pow_mat(M, n);
  160.  
  161. // for(int i=0; i<10; i++){
  162. // for(int j=0; j<10; j++){
  163. // printf("%d ", M.m[i][j]);
  164. // }puts("");
  165. // }puts("");
  166.  
  167. ;
  168. ; i<ac.Size; i++)
  169. ans += M.m[][i];
  170. ans %= MOD;
  171. printf("%d\n", ans);
  172. }
  173. ;
  174. }

瞎想 : AC自动机上的DP真的好难啊!!!

POJ 2778 DNA Sequence ( AC自动机、Trie图、矩阵快速幂、DP )的更多相关文章

  1. POJ 2778 DNA Sequence (AC自动机+DP+矩阵)

    题意:给定一些串,然后让你构造出一个长度为 m 的串,并且不包含以上串,问你有多少个. 析:很明显,如果 m 小的话 ,直接可以用DP来解决,但是 m 太大了,我们可以认为是在AC自动机图中,根据离散 ...

  2. poj 2778 DNA Sequence ac自动机+矩阵快速幂

    链接:http://poj.org/problem?id=2778 题意:给定不超过10串,每串长度不超过10的灾难基因:问在之后给定的长度不超过2e9的基因长度中不包含灾难基因的基因有多少中? DN ...

  3. poj 2778 DNA Sequence AC自动机DP 矩阵优化

    DNA Sequence Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 11860   Accepted: 4527 Des ...

  4. poj 2778 DNA Sequence AC自动机

    DNA Sequence Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 11860   Accepted: 4527 Des ...

  5. POJ 2778 DNA Sequence (AC自动机,矩阵乘法)

    题意:给定n个不能出现的模式串,给定一个长度m,要求长度为m的合法串有多少种. 思路:用AC自动机,利用AC自动机上的节点做矩阵乘法. #include<iostream> #includ ...

  6. poj2778 DNA Sequence【AC自动机】【矩阵快速幂】

    DNA Sequence Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 19991   Accepted: 7603 Des ...

  7. hdu2243 考研路茫茫——单词情结【AC自动机】【矩阵快速幂】

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

  8. POJ 2778 DNA Sequence (AC自己主动机 + dp)

    DNA Sequence 题意:DNA的序列由ACTG四个字母组成,如今给定m个不可行的序列.问随机构成的长度为n的序列中.有多少种序列是可行的(仅仅要包括一个不可行序列便不可行).个数非常大.对10 ...

  9. 【CF696D】Legen...(AC自动机)(矩阵快速幂)

    题目描述 Barney was hanging out with Nora for a while and now he thinks he may have feelings for her. Ba ...

随机推荐

  1. 实体类的[Serializable]标签造成WebAPI Post接收不到值

    WebAPI: [HttpPost] public HttpResponseMessage test([FromBody]List<Class1> list) { return Commo ...

  2. python2和python3中split的坑

    执行同样的split,python2和python3截取的行数内容却不一样 我想要截取Dalvik Heap行,使用split('\n')的方法 import os cpu='adb shell du ...

  3. 【MM系列】SAP PO增强BADI

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[MM系列]SAP PO增强BADI   前言部 ...

  4. keras recall

    # accuracy, fmeasure, precision,recall def mcor(y_true, y_pred): y_pred_pos = K.round(K.clip(y_pred, ...

  5. 【Windows Server存储】MBR和GPT分区表

    MBR和GPT分区表 分区表用于引导操作系统 master boot record(MBR)于1983年首次在PC上推出 最大4个主分区 2太空间 GUID Partition Table(GPT), ...

  6. linux 正则表达式 目录

    linux 通配符与正则表达式 linux 通配符 linux 正则表达式 使用grep命令 linux 扩展正则表达式 egrep linux 正则表达式 元字符

  7. [19/09/19-星期四] Python中的字典和集合

    一.字典 # 字典 # 使用 {} 来创建字典 d = {} # 创建了一个空字典 # 创建一个保护有数据的字典 # 语法: # {key:value,key:value,key:value} # 字 ...

  8. 图——图的Floyd法最短路径实现

    1,Dijkstra 算法一次性求得起始顶点到所有其它顶点的最短路径,如果想要求解任意两个顶点之间的最短路径,可将图中顶点作为起始顶点执行 n 次 Dijkstra 算法就可以了: 2,可能解决方案: ...

  9. ECMAScript 6 学习笔记(一)

    ECMAScript 6简介 ECMAScript 6.0(以下简称ES6)是JavaScript语言的下一代标准,已经在2015年6月正式发布了.它的目标,是使得JavaScript语言可以用来编写 ...

  10. 3.css3文字与字体

    1.css3文字与字体: ①Font-size:大小. ⑴通常使用px.百分比.em来设置大小: ⑵xx-small.x-small.small.medium.large.x-large.xx-lar ...