计蒜客 疑似病毒 (AC自动机 + 可达矩阵)
**链接 : ** 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'$ 就可以得到答案了.
**细节 : **
- 在初始化可达矩阵的时候, 对于任意一个节点, 它都能接受 $A, T, C, G$ 四个字符!!!, (因为这个地方wa了无数次).
- 搜索时从当前层究竟能调到第几层的条件是自动机的节点的cal_value的所决定的.
- 注意建立自动机的时候需要注意, 靠下的终态点需要加上它所有 $fail$ 指针的base_value.
补充 : ** 这道题的确非常有价值, 能够加深对AC自动机的理解, 而且还有将字符串匹配问题转为图的问题, 进而用可达矩阵**来表示连通性, 然后通过矩阵快速幂来求出 $l$ 长度的 $DNA$ 序列种类数.
**最后 : ** 待补充吧... 看起来理解还是不太够, 虽然听着思路能A, 但是找到方向才是更重要的事情!!!
/*************************************************************************
> File Name: ac_machine.cpp
> Author:
> Mail:
> Created Time: 2017年11月25日 星期六 11时01分11秒
************************************************************************/
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
const int MAX_N = 10000;
const int MAT_X = 200;
const int SIZE = 4;
const int BASE = '0';
const int LAYER = 3;
const int MOD = 10007;
int node_cnt = 1; // 统计节点数量用于分配index, 在insert中统计, 在automaton中分配
int mat_size;
// Matrix 结构声明
struct Matrix {
Matrix() {
memset(this->m, 0, sizeof(this->m));
}
int m[MAT_X][MAT_X];
void show() {
for (int i = 0 ; i < mat_size ; ++i) {
for (int j = 0 ; j < mat_size ; ++j) {
printf("%4d ", this->m[i][j]);
}
printf("\n");
}
}
};
Matrix unit_mat;
Matrix CGMatrix; // 可达矩阵
void init_unint_mat() {
for (int i = 0 ; i < MAT_X ; ++i) {
unit_mat.m[i][i] = 1;
}
}
Matrix operator* (const Matrix &a, const Matrix &b) {
Matrix c;
for (int i = 0 ; i < mat_size ; ++i) {
for (int j = 0 ; j < mat_size ; ++j) {
int sum = 0;
for (int k = 0 ; k < mat_size ; ++k) {
sum += (a.m[i][k] * b.m[k][j]);
sum %= MOD;
}
c.m[i][j] = sum % MOD;
}
}
return c;
}
// 快速幂 : 计算矩阵a的x次方
Matrix quick_mat_pow(Matrix a, int x) {
Matrix ret = unit_mat;
while (x) {
// TODO
if (x & 1) ret = ret * a;
a = a * a;
x >>= 1;
}
return ret;
}
// Trie 结构声明
// base_val : 基础值
// cal_val : 计算值
// index : 下标
typedef struct Trie {
int base_val;
int cal_val[LAYER];
int index[LAYER];
struct Trie *fail;
struct Trie **childs;
} Node, *Tree;
Trie* new_node() {
Trie *p = (Trie *)malloc(sizeof(Trie));
p->childs = (Trie **)malloc(sizeof(Trie *) * SIZE);
memset(p->childs, 0, sizeof(Trie *) * SIZE);
p->fail = NULL;
p->base_val = 0;
memset(p->index, 0, sizeof(int) * LAYER);
memset(p->cal_val, 0, sizeof(int) * LAYER);
return p;
}
void clear(Trie *node) {
if (node == NULL) return;
for (int i = 0 ; i < SIZE ; ++i) {
clear(node->childs[i]);
}
free(node->childs);
free(node);
}
void insert(Trie *node, char *str) {
Trie *p = node;
for (int i = 0 ; str[i] ; ++i) {
if (p->childs[str[i] - BASE] == NULL) {
p->childs[str[i] - BASE] = new_node();
// 更新节点数量
++node_cnt;
}
p = p->childs[str[i] - BASE];
}
p->base_val = 1;
}
// 建立自动机
void build_automaton(Trie *root) {
root->fail = NULL;
Trie *que[MAX_N];
int l = 0, r = 0, k = 0; // k用于计算index
que[r++] = root;
while (l < r) {
Trie *now = que[l++];
// 更新三层下标
now->index[0] = k;
now->index[1] = k + node_cnt;
now->index[2] = k + node_cnt * 2;
++k;
for (int i = 0 ; i < SIZE ; ++i) {
if (now->childs[i] == NULL) continue;
Trie *child = now->fail;
while (child && child->childs[i] == NULL) {
child = child->fail;
}
if (child == NULL) {
child = root;
} else {
child = child->childs[i];
}
now->childs[i]->fail = child;
now->childs[i]->base_val += now->childs[i]->fail->base_val;
que[r++] = now->childs[i];
}
}
}
// 判断在第几个自动机
int inLayer(int x) {
return (x <= 2 ? x : 2);
}
// 得到孩子的下标
int getChildIndex(Trie *now, Trie *child, int now_ind) {
return child->index[inLayer(now->cal_val[now_ind / node_cnt] + child->base_val)];
}
// 更新计算权值
int updataCalVal(Trie *now, Trie *child, int now_ind) {
return now->cal_val[now_ind / node_cnt] + child->base_val;
}
// BFS初始化可达矩阵
void BFS(Trie *root) {
Trie *que[MAX_N * 3];
int ind[MAX_N * 3];
int vis[MAX_N * 3] = {0};
int que_l = 0, que_r = 0;
int ind_l = 0, ind_r = 0;
que[que_r++] = root;
ind[ind_r++] = 0;
while (ind_l < ind_r) {
Trie *now = que[que_l++];
int now_ind = ind[ind_l++];
vis[now_ind] = 1;
for (int i = 0 ; i < SIZE ; ++i) {
Trie *child;
if (!now->childs[i]) {
// 寻找失败指针中是否出现childs[i], 如果没出现过, 那么就会走回root节点
Trie *p_fail = now->fail;
while (p_fail != NULL && p_fail->childs[i] == NULL) {
p_fail = p_fail->fail;
}
// 如果p_fail == NULL 那么这个一定为root
if (p_fail == NULL) {
child = root;
} else {
child = p_fail->childs[i];
}
} else {
child = now->childs[i];
}
int child_ind = getChildIndex(now, child, now_ind);
child->cal_val[child_ind / node_cnt] = updataCalVal(now, child, now_ind);
CGMatrix.m[now_ind][child_ind]++;
if (vis[child_ind] == 0) {
ind[ind_r++] = child_ind;
que[que_r++] = child;
vis[child_ind] = 1;
}
}
}
}
// 转换函数 : 将ATCG转换为0123
void transStr(char *str) {
for (int j = 0 ; str[j] ; ++j) {
switch(str[j]) {
case 'A' :
str[j] = '0';
break;
case 'T' :
str[j] = '1';
break;
case 'C' :
str[j] = '2';
break;
case 'G' :
str[j] = '3';
break;
}
}
}
int main() {
int n, L;
char str[200];
while (scanf("%d%d", &n, &L) != EOF) {
Trie *root = new_node();
node_cnt = 1;
for (int i = 0 ; i < n ; ++i) {
getchar();
scanf("%s", str);
transStr(str);
insert(root, str);
}
build_automaton(root);
// 设置矩阵大小
mat_size = node_cnt * 3;
init_unint_mat();
BFS(root);
CGMatrix = quick_mat_pow(CGMatrix, L);
int ans = 0;
for (int j = 2 * node_cnt ; j < mat_size ; ++j) {
ans += CGMatrix.m[0][j];
ans %= MOD;
}
printf("%d\n", ans);
memset(CGMatrix.m, 0, sizeof(CGMatrix.m));
clear(root);
}
return 0;
}
计蒜客 疑似病毒 (AC自动机 + 可达矩阵)的更多相关文章
- 计蒜客模拟赛D1T1 蒜头君打地鼠:矩阵旋转+二维前缀和
题目链接:https://nanti.jisuanke.com/t/16445 题意: 给你一个n*n大小的01矩阵,和一个k*k大小的锤子,锤子只能斜着砸,问只砸一次最多能砸到多少个1. 题解: 将 ...
- 计蒜客:Entertainment Box
Ada, Bertrand and Charles often argue over which TV shows to watch, and to avoid some of their fight ...
- 计蒜客 31436 - 提高水平 - [状压DP]
题目链接:https://nanti.jisuanke.com/t/31436 作为一名车手,为了提高自身的姿势水平,平时的练习是必不可少的.小 J 每天的训练包含 $N$ 个训练项目,他会按照某个顺 ...
- 计蒜客 31434 - 广场车神 - [DP+前缀和]
题目链接:https://nanti.jisuanke.com/t/31434 小 D 是一位著名的车手,他热衷于在广场上飙车.每年儿童节过后,小 D 都会在广场上举行一场别样的车技大赛. 小 D 所 ...
- 计蒜客 A1607 UVALive 8512 [ACM-ICPC 2017 Asia Xi'an]XOR
ICPC官网题面假的,要下载PDF,点了提交还找不到结果在哪看(我没找到),用VJ交还直接return 0;也能AC 计蒜客题面 这个好 Time limit 3000 ms OS Linux 题目来 ...
- 计蒜客 作弊揭发者(string的应用)
鉴于我市拥堵的交通状况,市政交管部门经过听证决定在道路两侧安置自动停车收费系统.当车辆驶入车位,系统会通过配有的摄像头拍摄车辆画面,通过识别车牌上的数字.字母序列识别车牌,通过连接车管所车辆信息数据库 ...
- 计蒜客的一道题dfs
这是我无聊时在计蒜客发现的一道题. 题意: 蒜头君有一天闲来无事和小萌一起玩游戏,游戏的内容是这样的:他们不知道从哪里找到了N根不同长度的木棍, 看谁能猜出这些木棍一共能拼出多少个不同的不等边三角形. ...
- 计蒜客模拟赛5 D2T1 成绩统计
又到了一年一度的新生入学季了,清华和北大的计算机系同学都参加了同一场开学考试(因为两校兄弟情谊深厚嘛,来一场联考还是很正常的). 不幸的是,正当老师要统计大家的成绩时,世界上的所有计算机全部瘫痪了. ...
- 计蒜客 等边三角形 dfs
题目: https://www.jisuanke.com/course/2291/182238 思路: 1.dfs(int a,int b,int c,int index)//a,b,c三条边的边长, ...
随机推荐
- HDU5501/BestCoder Round #59 (div.2)The Highest Mark dp+贪心
The Highest Mark 问题描述 2045年的SD省队选拔,赛制和三十年前已是完全不同.一场比赛的比赛时间有 tt 分钟,有 nn 道题目. 第 ii 道题目的初始分值为 A_i(A_i \ ...
- [译]使用AssetBundle Manader
AssetBundle and the AssetBundle Manager 介绍 AssetBundle允许从本地或者远程服务器加载Assets资源,利用AssetBundles技术,Assets ...
- 布局技巧4:使用ViewStub
多亏了<include />标签,在Android里,很容易就能做到共享和重用UI组件.在Android开发中,很容易就能创建出复杂的UI结构,结果呢,用了很多的View,且其中的一些很少 ...
- 解决:sql2005 安装完后 没有服务的问题
去下面网站下载SQLEXPR_CHS.EXE然后安装就ok了.http://www.microsoft.com/downloadS/details.aspx?familyid=220549B5-0B0 ...
- (多项式)因式分解定理(Factor theorem)与多项式剩余定理(Polynomial remainder theorem)(多项式长除法)
(多项式的)因式分解定理(factor theorem)是多项式剩余定理的特殊情况,也就是余项为 0 的情形. 0. 多项式长除法(Polynomial long division) Polynomi ...
- javamail - 协议SMTP\IMAP\POP3设置
资料来自:https://www.tutorialspoint.com/javamail_api/index.htm [SMTP - Simple Mail Transfer Protocol] Na ...
- word文档在线预览解决方案
花了一整天在网上翻关于 “word文档在线预览解决方案” 相关的资料,感觉实现难度比较大还是用PDF来解决好了.. 下面列一下比较好的参考资料吧 参考资料 前端实现在线预览pdf.word.xls.p ...
- html5做的一个激光条
<!DOCTYPE HTML><html lang="zh-cn"><head> <title>CSS3激光加载条</titl ...
- Parameter index out of range (1 > number of parameters, which is 0).
数据库错误:Parameter index out of range (1 > number of parameters, which is 0) ...
- const修饰规则 及其 用法
const指针和指向const变量的指针,在写法上容易让人混淆,记住一个规则:从左至右,依次结合,const就近结合. 比如,int * const p: 1.int * (const p):变量p经 ...