You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

98 lines
4.1 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 1010; // 致病DNA片段的数量上限50每个DNA片段的长度不超过20所以Trie树的结点个数最多不超过50*20=1000个节点
int n, m;
int tr[N][4], cnt[N], idx; // Trie树每个节点最多四条出边ATGC映射就0123,cnt表示每个节点为结尾的结束标识idx配合创建Trie树的导航员
int q[N], ne[N]; // AC自动机需要的队列数组失配指针数组,ne[u]描述在u结点失配时找到root~u的最长后缀相同的最长前缀root~v
char str[N]; // 第一轮当作模板串的录入存储器完成构建Trie树后第二轮当做文本串的存储器
int f[N][N]; // f[i][j]:DNA扫描到第i个字母且当前走到了trie里面的第j个节点 的 所有操作方案中,最少的修改数量
int get(char c) {
if (c == 'A') return 0;
if (c == 'T') return 1;
if (c == 'G') return 2;
return 3;
}
void insert() { // 利用模板串构建Trie树
int p = 0;
for (int i = 0; str[i]; i++) {
int t = get(str[i]);
if (tr[p][t] == 0) tr[p][t] = ++idx;
p = tr[p][t];
}
cnt[p] = 1; // 记录结束标识
}
// 构建AC自动机
void bfs() {
int hh = 0, tt = -1;
for (int i = 0; i < 4; i++) // 利用bfs进行构建把第一层的节点入队列
if (tr[0][i]) q[++tt] = tr[0][i];
while (hh <= tt) {
int p = q[hh++];
for (int i = 0; i < 4; i++) { // 尝试四个出边
int &c = tr[p][i];
if (!c)
c = tr[ne[p]][i];
else {
ne[c] = tr[ne[p]][i];
q[++tt] = c;
// c是不是终止节点有两个判断来源
// 1、自己本身就是模示串A的终点
// 2、它的失配指针指向的结点是模示串B的终点
cnt[c] |= cnt[ne[c]];
}
}
}
}
int main() {
int T = 1;
while (scanf("%d", &n), n) {
memset(tr, 0, sizeof tr);
memset(cnt, 0, sizeof cnt);
memset(ne, 0, sizeof ne);
idx = 0;
for (int i = 0; i < n; i++) {
scanf("%s", str);
insert();
}
// 构建AC自动机
bfs();
// str:长度不超过1000的非空字符串字符串中仅包含字符A, G , C , T用以表示待修复DNA片段
// Q:为什么不直接从索引0开始读入呢
// 答你可以尝试以索引0开始读入你会发现后面的状态转移方程就无法使用f[i-1][j]
// 因为数组下标越界了~哈哈,看来啊,都是先有状态转移方程,后有初始值,枚举序等东西
scanf("%s", str + 1);
m = strlen(str + 1); // 长串的长度
memset(f, 0x3f, sizeof f); // 初始化动态规划数组,预求最小,先设最大
f[0][0] = 0; // 匹配了前0个字符在Trie树中的节点号是0的情况下此时代价为0
// 开始DP填表一维是长串走到哪个字符二维是AC自动机中走到哪个节点
for (int i = 0; i < m; i++) // 枚举长串字符
for (int j = 0; j <= idx; j++) // 枚举节点
for (int k = 0; k < 4; k++) { // AGCT四种字符
int t = get(str[i + 1]) != k; // 下一个字符str[i+1],如果与目标字符一致代价为0否则代价为1
int p = tr[j][k]; // fail失配指针指向位置
// 如果p节点不是病毒DNA的终止节点那么它的最小代价可以由f[i][j]+t转移
if (!cnt[p]) f[i + 1][p] = min(f[i + 1][p], f[i][j] + t);
}
// 找出最小值
int res = INF;
for (int i = 0; i <= idx; i++) res = min(res, f[m][i]);
// 没有最小值,返回-1
if (res == INF) res = -1;
printf("Case %d: %d\n", T++, res);
}
return 0;
}