题意描述
POJ 链接
生物学家终于发明了修复 DNA
的技术,能够将包含各种遗传疾病的DNA片段进行修复。
为了简单起见,DNA 看作是一个由
A、G、C、T
构成的字符串。
修复技术就是通过改变字符串中的一些字符,从而消除字符串中包含的致病片段。
例如,我们可以通过改变两个字符,将 DNA 片段 AAGCAG 变为
AGGCAC,从而使得 DNA 片段中不再包含致病片段
AAG、AGC、CAG,以达到修复该DNA片段的目的。
需注意,被修复的 DNA 片段中,仍然只能包含字符
A、G、C、T。
请你帮助生物学家修复给定的 DNA
片段,并且修复过程中改变的字符数量要尽可能的少。
解题思路
本题存在多个字符串的匹配问题,可以考虑使用 AC
自动机解决。我们可以先将所有的致病片段都插入 AC
自动机中,并将致病基因字符串作标记。注意如果一个字符串的子串是致病基因,则这个字符串必为致病基因。故构建
AC 自动机时需要看 fail
所指的字符串是否为致病基因,然后更新该节点的信息。即
c->isWord |= c->fail->isWord。
接下来我们需要求解问题。由于求的是最小修改次数,可考虑 DP
解决。由于可使用 AC 自动机匹配是否有子串为致病基因,我们可以考虑以 AC
自动机匹配时的状态为维度进行 DP。
对于需要修复的 DNA 片段 \(s\) ,我们可定义 \(f_{i, j}\) 为遍历到 \(s_i\) ,且达到位于 AC 自动机 \(j\) 节点的状态时需要最小修改次数。对于可从
\(j\) 到达的状态 \(k\) ,我们设 \(l\) 为 \(k\)
所在的字符。设属于致病基因的字符串所代表的节点的集合为 \(\text{Disease}\) ,于是有下列转移方程:
\[
f_{i + 1, k} = \begin{cases}
\min\{ f_{i + 1, k}, f_{i, j} + [s_{i + 1} = l] \} & k \notin
\text{Disaese} \\
\infty & k \in \text{Disease}
\end{cases}
\]
接下来我们只需要初始化 \(f_{i, j} =
\begin{cases}
0 & i = 0 \wedge j = \text{root} \\
\infty & otherwise.
\end{cases}\) ,然后求解即可。设 AC 自动机中所有点的集合为 \(\text{AC}\) ,最终答案则为 \(\min\limits_{i \in \text{AC}}{f_{m,
i}}\) 。
代码演示
由于这里需要枚举 AC 自动机中的所有节点,故使用内存池编写。
POJ 不支持 C++11 真 TM 烦
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 #include <cstdio> #include <cstring> #include <climits> #include <vector> #include <queue> #include <new> const int MAXN = 1000 ;const int CHARSET_SIZE = 4 ;inline int get (const char c) { if (c == 'A' ) return 0 ; else if (c == 'G' ) return 1 ; else if (c == 'C' ) return 2 ; else return 3 ; } struct Trie { struct Node { int id; Node *c[CHARSET_SIZE], *fail; bool isWord; Node (bool isWord = false ) : fail (NULL ), isWord (isWord) { for (int i = 0 ; i < CHARSET_SIZE; i++) c[i] = NULL ; } Node (int id, bool isWord = false ) : id (id), fail (NULL ), isWord (isWord) { for (int i = 0 ; i < CHARSET_SIZE; i++) c[i] = NULL ; } } *root, _pool[MAXN + 1 ], *_curr; int idx; Trie () : root (_pool), _curr(_pool + 1 ), idx (0 ) { root->id = 0 ; } Node *insert (const char *begin, const char *end) { Node **v = &root; for (const char *p = begin; p != end; p++) { if (!*v) *v = new (_curr++) Node (++idx); v = &(*v)->c[get (*p)]; } if (!*v) *v = new (_curr++) Node (++idx); (*v)->isWord = true ; return *v; } void build () { std::queue<Node *> q; q.push (root); root->fail = root; while (!q.empty ()) { Node *v = q.front (); q.pop (); for (int i = 0 ; i < CHARSET_SIZE; i++) { Node *&c = v->c[i]; if (!c) { c = v == root ? root : v->fail->c[i]; continue ; } Node *u = v->fail; c->fail = v != root && u->c[i] ? u->c[i] : root; q.push (c); c->isWord |= c->fail->isWord; } } } }; int main () { int n, counts = 0 ; while (scanf ("%d" , &n) != EOF && n) { static char s[MAXN + 1 ]; Trie t; std::vector<Trie::Node *> node (n) ; for (int i = 0 ; i < n; i++) { scanf ("%s" , s); node[i] = t.insert (s, s + strlen (s)); } t.build (); scanf ("%s" , s); int m = strlen (s); std::vector< std::vector<int > > f (m + 1 , std::vector <int >(t.idx + 1 , INT_MAX)); f[0 ][0 ] = 0 ; for (int i = 0 ; i < m; i++) { for (int j = 0 ; j <= t.idx; j++) { if (f[i][j] != INT_MAX) { for (int k = 0 ; k < CHARSET_SIZE; k++) { int l = get (s[i]) != k; Trie::Node *p = t._pool[j].c[k]; if (!p->isWord) f[i + 1 ][p->id] = std::min (f[i + 1 ][p->id], f[i][j] + l); } } } } int ans = INT_MAX; for (int i = 0 ; i <= t.idx; i++) ans = std::min (ans, f[m][i]); printf ("Case %d: %d\n" , ++counts, ans == INT_MAX ? -1 : ans); } return 0 ; }