**链接 : ** Here!

背景 : ** 开始我同学是用 AC自动机 + DP 的方法来做这道题, 这道题的标签是 AC自动机, 动态规划, 矩阵, 按道理来说 AC自动机 + DP 应该是能过的, 但是他不幸的 $T$ 了, $QAQ$, 后来神犇Hug**给我们提供了一个思路!!!

思路 : ** 题目要求是 "如果其中两个或者两个以上**的 $DNA$ 序列是一个 $DNA$ 序列 $A$ 的子串,那么 $DNA$ 序列 $A$ 是疑似病毒的 $DNA$ 序列", 那么也就是说字符串 $A$, 它要是能在自动机中走到两次终态, 那么它就是疑似病毒的 $DNA$ 序列. 因为AC自动机可以等价转化为一个图, 所以说这个问题就变成了, 从 $root$ 节点出发, 在一个图中 $l$ 步通过终态点两次或者两次以上的方法数....................!!! $GG$

神犇告诉我们, 可以一个自动机在逻辑层面抽象成三层自动机, 然后用搜索来初始化可达矩阵, 这个可达矩阵$Matrix[i][j] = cnt$ 代表从 $i$ 点经过 $1$ 步到达 $j$ 点的方法数为 $cnt$. 然后将初始的可达矩阵求 $l$ 次方, 就可以得到一个增量矩阵 $Matrix'$ , 最后只需要用初始的矩阵 $[1, 0, 0, ....] * Matrix'$ 就可以得到答案了.

**细节 : **

  1. 在初始化可达矩阵的时候, 对于任意一个节点, 它都能接受 $A, T, C, G$ 四个字符!!!, (因为这个地方wa了无数次).
  2. 搜索时从当前层究竟能调到第几层的条件是自动机的节点的cal_value的所决定的.
  3. 注意建立自动机的时候需要注意, 靠下的终态点需要加上它所有 $fail$ 指针的base_value.

补充 : ** 这道题的确非常有价值, 能够加深对AC自动机的理解, 而且还有将字符串匹配问题转为图的问题, 进而用可达矩阵**来表示连通性, 然后通过矩阵快速幂来求出 $l$ 长度的 $DNA$ 序列种类数.

**最后 : ** 待补充吧... 看起来理解还是不太够, 虽然听着思路能A, 但是找到方向才是更重要的事情!!!


  1. /*************************************************************************
  2. > File Name: ac_machine.cpp
  3. > Author:
  4. > Mail:
  5. > Created Time: 2017年11月25日 星期六 11时01分11秒
  6. ************************************************************************/
  7. #include <cstdio>
  8. #include <cstring>
  9. #include <cstdlib>
  10. #include <iostream>
  11. #include <algorithm>
  12. using namespace std;
  13. const int MAX_N = 10000;
  14. const int MAT_X = 200;
  15. const int SIZE = 4;
  16. const int BASE = '0';
  17. const int LAYER = 3;
  18. const int MOD = 10007;
  19. int node_cnt = 1; // 统计节点数量用于分配index, 在insert中统计, 在automaton中分配
  20. int mat_size;
  21. // Matrix 结构声明
  22. struct Matrix {
  23. Matrix() {
  24. memset(this->m, 0, sizeof(this->m));
  25. }
  26. int m[MAT_X][MAT_X];
  27. void show() {
  28. for (int i = 0 ; i < mat_size ; ++i) {
  29. for (int j = 0 ; j < mat_size ; ++j) {
  30. printf("%4d ", this->m[i][j]);
  31. }
  32. printf("\n");
  33. }
  34. }
  35. };
  36. Matrix unit_mat;
  37. Matrix CGMatrix; // 可达矩阵
  38. void init_unint_mat() {
  39. for (int i = 0 ; i < MAT_X ; ++i) {
  40. unit_mat.m[i][i] = 1;
  41. }
  42. }
  43. Matrix operator* (const Matrix &a, const Matrix &b) {
  44. Matrix c;
  45. for (int i = 0 ; i < mat_size ; ++i) {
  46. for (int j = 0 ; j < mat_size ; ++j) {
  47. int sum = 0;
  48. for (int k = 0 ; k < mat_size ; ++k) {
  49. sum += (a.m[i][k] * b.m[k][j]);
  50. sum %= MOD;
  51. }
  52. c.m[i][j] = sum % MOD;
  53. }
  54. }
  55. return c;
  56. }
  57. // 快速幂 : 计算矩阵a的x次方
  58. Matrix quick_mat_pow(Matrix a, int x) {
  59. Matrix ret = unit_mat;
  60. while (x) {
  61. // TODO
  62. if (x & 1) ret = ret * a;
  63. a = a * a;
  64. x >>= 1;
  65. }
  66. return ret;
  67. }
  68. // Trie 结构声明
  69. // base_val : 基础值
  70. // cal_val : 计算值
  71. // index : 下标
  72. typedef struct Trie {
  73. int base_val;
  74. int cal_val[LAYER];
  75. int index[LAYER];
  76. struct Trie *fail;
  77. struct Trie **childs;
  78. } Node, *Tree;
  79. Trie* new_node() {
  80. Trie *p = (Trie *)malloc(sizeof(Trie));
  81. p->childs = (Trie **)malloc(sizeof(Trie *) * SIZE);
  82. memset(p->childs, 0, sizeof(Trie *) * SIZE);
  83. p->fail = NULL;
  84. p->base_val = 0;
  85. memset(p->index, 0, sizeof(int) * LAYER);
  86. memset(p->cal_val, 0, sizeof(int) * LAYER);
  87. return p;
  88. }
  89. void clear(Trie *node) {
  90. if (node == NULL) return;
  91. for (int i = 0 ; i < SIZE ; ++i) {
  92. clear(node->childs[i]);
  93. }
  94. free(node->childs);
  95. free(node);
  96. }
  97. void insert(Trie *node, char *str) {
  98. Trie *p = node;
  99. for (int i = 0 ; str[i] ; ++i) {
  100. if (p->childs[str[i] - BASE] == NULL) {
  101. p->childs[str[i] - BASE] = new_node();
  102. // 更新节点数量
  103. ++node_cnt;
  104. }
  105. p = p->childs[str[i] - BASE];
  106. }
  107. p->base_val = 1;
  108. }
  109. // 建立自动机
  110. void build_automaton(Trie *root) {
  111. root->fail = NULL;
  112. Trie *que[MAX_N];
  113. int l = 0, r = 0, k = 0; // k用于计算index
  114. que[r++] = root;
  115. while (l < r) {
  116. Trie *now = que[l++];
  117. // 更新三层下标
  118. now->index[0] = k;
  119. now->index[1] = k + node_cnt;
  120. now->index[2] = k + node_cnt * 2;
  121. ++k;
  122. for (int i = 0 ; i < SIZE ; ++i) {
  123. if (now->childs[i] == NULL) continue;
  124. Trie *child = now->fail;
  125. while (child && child->childs[i] == NULL) {
  126. child = child->fail;
  127. }
  128. if (child == NULL) {
  129. child = root;
  130. } else {
  131. child = child->childs[i];
  132. }
  133. now->childs[i]->fail = child;
  134. now->childs[i]->base_val += now->childs[i]->fail->base_val;
  135. que[r++] = now->childs[i];
  136. }
  137. }
  138. }
  139. // 判断在第几个自动机
  140. int inLayer(int x) {
  141. return (x <= 2 ? x : 2);
  142. }
  143. // 得到孩子的下标
  144. int getChildIndex(Trie *now, Trie *child, int now_ind) {
  145. return child->index[inLayer(now->cal_val[now_ind / node_cnt] + child->base_val)];
  146. }
  147. // 更新计算权值
  148. int updataCalVal(Trie *now, Trie *child, int now_ind) {
  149. return now->cal_val[now_ind / node_cnt] + child->base_val;
  150. }
  151. // BFS初始化可达矩阵
  152. void BFS(Trie *root) {
  153. Trie *que[MAX_N * 3];
  154. int ind[MAX_N * 3];
  155. int vis[MAX_N * 3] = {0};
  156. int que_l = 0, que_r = 0;
  157. int ind_l = 0, ind_r = 0;
  158. que[que_r++] = root;
  159. ind[ind_r++] = 0;
  160. while (ind_l < ind_r) {
  161. Trie *now = que[que_l++];
  162. int now_ind = ind[ind_l++];
  163. vis[now_ind] = 1;
  164. for (int i = 0 ; i < SIZE ; ++i) {
  165. Trie *child;
  166. if (!now->childs[i]) {
  167. // 寻找失败指针中是否出现childs[i], 如果没出现过, 那么就会走回root节点
  168. Trie *p_fail = now->fail;
  169. while (p_fail != NULL && p_fail->childs[i] == NULL) {
  170. p_fail = p_fail->fail;
  171. }
  172. // 如果p_fail == NULL 那么这个一定为root
  173. if (p_fail == NULL) {
  174. child = root;
  175. } else {
  176. child = p_fail->childs[i];
  177. }
  178. } else {
  179. child = now->childs[i];
  180. }
  181. int child_ind = getChildIndex(now, child, now_ind);
  182. child->cal_val[child_ind / node_cnt] = updataCalVal(now, child, now_ind);
  183. CGMatrix.m[now_ind][child_ind]++;
  184. if (vis[child_ind] == 0) {
  185. ind[ind_r++] = child_ind;
  186. que[que_r++] = child;
  187. vis[child_ind] = 1;
  188. }
  189. }
  190. }
  191. }
  192. // 转换函数 : 将ATCG转换为0123
  193. void transStr(char *str) {
  194. for (int j = 0 ; str[j] ; ++j) {
  195. switch(str[j]) {
  196. case 'A' :
  197. str[j] = '0';
  198. break;
  199. case 'T' :
  200. str[j] = '1';
  201. break;
  202. case 'C' :
  203. str[j] = '2';
  204. break;
  205. case 'G' :
  206. str[j] = '3';
  207. break;
  208. }
  209. }
  210. }
  211. int main() {
  212. int n, L;
  213. char str[200];
  214. while (scanf("%d%d", &n, &L) != EOF) {
  215. Trie *root = new_node();
  216. node_cnt = 1;
  217. for (int i = 0 ; i < n ; ++i) {
  218. getchar();
  219. scanf("%s", str);
  220. transStr(str);
  221. insert(root, str);
  222. }
  223. build_automaton(root);
  224. // 设置矩阵大小
  225. mat_size = node_cnt * 3;
  226. init_unint_mat();
  227. BFS(root);
  228. CGMatrix = quick_mat_pow(CGMatrix, L);
  229. int ans = 0;
  230. for (int j = 2 * node_cnt ; j < mat_size ; ++j) {
  231. ans += CGMatrix.m[0][j];
  232. ans %= MOD;
  233. }
  234. printf("%d\n", ans);
  235. memset(CGMatrix.m, 0, sizeof(CGMatrix.m));
  236. clear(root);
  237. }
  238. return 0;
  239. }

计蒜客 疑似病毒 (AC自动机 + 可达矩阵)的更多相关文章

  1. 计蒜客模拟赛D1T1 蒜头君打地鼠:矩阵旋转+二维前缀和

    题目链接:https://nanti.jisuanke.com/t/16445 题意: 给你一个n*n大小的01矩阵,和一个k*k大小的锤子,锤子只能斜着砸,问只砸一次最多能砸到多少个1. 题解: 将 ...

  2. 计蒜客:Entertainment Box

    Ada, Bertrand and Charles often argue over which TV shows to watch, and to avoid some of their fight ...

  3. 计蒜客 31436 - 提高水平 - [状压DP]

    题目链接:https://nanti.jisuanke.com/t/31436 作为一名车手,为了提高自身的姿势水平,平时的练习是必不可少的.小 J 每天的训练包含 $N$ 个训练项目,他会按照某个顺 ...

  4. 计蒜客 31434 - 广场车神 - [DP+前缀和]

    题目链接:https://nanti.jisuanke.com/t/31434 小 D 是一位著名的车手,他热衷于在广场上飙车.每年儿童节过后,小 D 都会在广场上举行一场别样的车技大赛. 小 D 所 ...

  5. 计蒜客 A1607 UVALive 8512 [ACM-ICPC 2017 Asia Xi'an]XOR

    ICPC官网题面假的,要下载PDF,点了提交还找不到结果在哪看(我没找到),用VJ交还直接return 0;也能AC 计蒜客题面 这个好 Time limit 3000 ms OS Linux 题目来 ...

  6. 计蒜客 作弊揭发者(string的应用)

    鉴于我市拥堵的交通状况,市政交管部门经过听证决定在道路两侧安置自动停车收费系统.当车辆驶入车位,系统会通过配有的摄像头拍摄车辆画面,通过识别车牌上的数字.字母序列识别车牌,通过连接车管所车辆信息数据库 ...

  7. 计蒜客的一道题dfs

    这是我无聊时在计蒜客发现的一道题. 题意: 蒜头君有一天闲来无事和小萌一起玩游戏,游戏的内容是这样的:他们不知道从哪里找到了N根不同长度的木棍, 看谁能猜出这些木棍一共能拼出多少个不同的不等边三角形. ...

  8. 计蒜客模拟赛5 D2T1 成绩统计

    又到了一年一度的新生入学季了,清华和北大的计算机系同学都参加了同一场开学考试(因为两校兄弟情谊深厚嘛,来一场联考还是很正常的). 不幸的是,正当老师要统计大家的成绩时,世界上的所有计算机全部瘫痪了. ...

  9. 计蒜客 等边三角形 dfs

    题目: https://www.jisuanke.com/course/2291/182238 思路: 1.dfs(int a,int b,int c,int index)//a,b,c三条边的边长, ...

随机推荐

  1. elasticsearch 布尔过滤器 游标查询 Scroll

    组合过滤器 | Elasticsearch: 权威指南 | Elastic https://www.elastic.co/guide/cn/elasticsearch/guide/current/co ...

  2. 20170410 --- Linux备课资料 --- 压缩与解压缩

    这节课我们来学习一下压缩与解压缩,那什么是压缩与解压缩呢? 联想一下Windows系统: 选中文件,右键选择即可 如果压缩,可以选择要压缩的格式,而解压缩直接选择就可以完成了 Linux是通过命令的方 ...

  3. luogu1040 加分二叉树

    题目大意 设一个n个节点的二叉树tree的中序遍历为(l,2,3,…,n),其中数字1,2,3,…,n为节点编号.每个节点都有一个分数(均为正整数),记第j个节点的分数为di,tree及它的每个子树都 ...

  4. YTU 2706: 编写一个函数求最大的n值

    2706: 编写一个函数求最大的n 值. 时间限制: 1 Sec  内存限制: 128 MB 提交: 341  解决: 132 题目描述 编写一个函数求满足以下条件的最大的n.:12+22+32+-+ ...

  5. Faas 典型场景——应用负载有显著的波峰波谷,典型用例-基于事件的数据处理

    Serverless适用的两大场景 场景一:应用负载有显著的波峰波谷 Serverless化与否的评判标准并不是公司规模的大小,而是其业务背后的具体技术问题,比如业务波峰波谷明显,如何实现削峰填谷.一 ...

  6. MSP430 G2553 Timer 中断总结

    目前总共用到了四个中断向量,我觉得已经把G2553的所有定时器中断都用到了. 定时器有两个,TA0与TA1,每个定时器又有两个中断向量 1,CCR0到达时的中断,在计数模式时候很有用,平时定时器的基本 ...

  7. PCB ODB++(Gerber)图形绘制实现方法

    这里讲解一下用net解析PCB图形绘制实现方法 一.解析PCB图形绘制实现 解析PCB图形,说简单也非常简单,先说一下,PCB Gerber图形由:点,线,弧,铜皮,文字 5类元素组成,通常简写为:P ...

  8. In 7-bit

    http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3713 题意:给定一个字符串,首先输出这个字符串的长度(以两位的十六进制的形 ...

  9. vue中时间控件绑定多个输入框

    首先去下载laydate时间控件,引入到相应的模板中 <input type="text" val-required="" value="&qu ...

  10. [Swift通天遁地]三、手势与图表-(1)监听屏幕上触摸事件的各种状态

    ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...