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