Files
python/TangDou/AcWing/Statemachine/1053.cpp
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

98 lines
4.1 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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;
}