【ACWing】1053. 修复DNA

题目地址:

https://www.acwing.com/problem/content/1055/

生物学家终于发明了修复DNA的技术,能够将包含各种遗传疾病的DNA片段进行修复。为了简单起见,DNA看作是一个由’A’, ‘G’ , ‘C’ , ‘T’构成的字符串。修复技术就是通过改变字符串中的一些字符,从而消除字符串中包含的致病片段。例如,我们可以通过改变两个字符,将DNA片段”AAGCAG”变为”AGGCAC”,从而使得DNA片段中不再包含致病片段”AAG”,”AGC”,”CAG”,以达到修复该DNA片段的目的。需注意,被修复的DNA片段中,仍然只能包含字符’A’, ‘G’, ‘C’, ‘T’。请你帮助生物学家修复给定的DNA片段,并且修复过程中改变的字符数量要尽可能的少。

输入格式:
输入包含多组测试数据。
每组数据第一行包含整数 N N N,表示致病DNA片段的数量。
接下来 N N N行,每行包含一个长度不超过 20 20 20的非空字符串,字符串中仅包含字符’A’, ‘G’ , ‘C’ , ‘T’,用以表示致病DNA片段。
再一行,包含一个长度不超过 1000 1000 1000的非空字符串 s s s,字符串中仅包含字符’A’, ‘G’ , ‘C’ , ‘T’,用以表示待修复DNA片段。
最后一组测试数据后面跟一行,包含一个 0 0 0,表示输入结束。

输出格式:
每组数据输出一个结果,每个结果占一行。
输入形如Case x: y,其中 x x x为测试数据编号(从 1 1 1开始), y y y为修复过程中所需改变的字符数量的最小值,如果无法修复给定DNA片段,则 y y y − 1 -1 1

数据范围:
1 ≤ N ≤ 50 1≤N≤50 1N50

设字母表只有 A C G T ACGT ACGT四个字母,其实就是给定若干模式串,再给定一个主串,问主串修改多少个位置,可以使得主串不含任一模式串作为子串。

先将所有模式串做成一个AC自动机,设 f [ i ] [ u ] f[i][u] f[i][u]是在自动机上走了 i i i步停留在 u u u节点的时候,最少要修改多少个字符能使得主串不含任一模式串。在自动机上走的过程,其实就是扫描主串的过程,把在自动机上走一个不同于当前扫描字符的边视为一单位的代价,那么问题其实就是问,要求路上走不到任何匹配点的情况下,在自动机上走 l s l_s ls步的最小代价是多少,视为 f [ i ] [ u ] f[i][u] f[i][u]的”新定义“。那么我们可以枚举 u u u的四条出边(对应四个不同字母),首先找到不产生匹配的出边,假设 u → p u\to p up并且 p p p不产生匹配,设 s s s下标从 1 1 1开始,那么有 f [ i + 1 ] [ p ] = min ⁡ u → p { f [ i ] [ u ] + 1 s i + 1 ≠ u → p } f[i+1][p]=\min_{u\to p}\{f[i][u]+1_{s_{i+1}\ne u\to p}\} f[i+1][p]=upmin{f[i][u]+1si+1=up}代码如下:

#include <iostream>
#include <cstring>
using namespace std;

const int N = 1010;
int n, m;
int tr[N][4], idx;
bool word[N];
int q[N], ne[N];
char s[N];
int mp['T' + 1];
int f[N][N];

void insert() {
  int p = 0;
  for (int i = 0; s[i]; i++) {
    int c = mp[s[i]];
    if (!tr[p][c]) tr[p][c] = ++idx;
    p = tr[p][c];
  }
  
  // 标记一下p点为匹配点,匹配点还有p的ne指针反向上的所有点,也要标记,见下文
  word[p] = true;
}

void build() {
  int hh = 0, tt = 0;
  for (int i = 0; i < 4; i++)
    if (tr[0][i]) q[tt++] = tr[0][i];
  
  while (hh < tt) {
    int t = q[hh++];
    for (int i = 0; i < 4; i++) {
      int p = tr[t][i];
      // 需要做word[p] |= word[ne[p]]这一步,如果ne[p]是匹配点,p点也是匹配点,
      if (p) ne[p] = tr[ne[t]][i], q[tt++] = p, word[p] |= word[ne[p]];
      else tr[t][i] = tr[ne[t]][i];
    }
  }
}

int main() {
  // 四个字母对应的边的下标
  mp['A'] = 0, mp['G'] = 1, mp['C'] = 2, mp['T'] = 3;
  int T = 1;
  while (scanf("%d", &n), n) {
    memset(tr, 0, sizeof tr);
    memset(word, 0, sizeof word);
    memset(ne, 0, sizeof ne);
    idx = 0;

    for (int i = 0; i < n; i++) {
      scanf("%s", s);
      insert();
    }

    build();

    scanf("%s", s + 1);
    m = strlen(s + 1);

    memset(f, 0x3f, sizeof f);
    f[0][0] = 0;
    // 遍历主串,即遍历步数
    for (int i = 0; i < m; i++)
      // 遍历所有节点,j是在自动机上走了i步走到的点
      for (int j = 0; j <= idx; j++) {
      	// 如果j节点是匹配点,则不能走,直接略过
      	// 如果j走不到,也直接略过
        if (word[j] || f[i][j] == 0x3f3f3f3f) continue;
        // 枚举4个方向
        for (int k = 0; k < 4; k++) {
          int p = tr[j][k];
          // p是匹配点,不能走
          if (word[p]) continue;
          // 更新i + 1步走到p的最小代价
          f[i + 1][p] = min(f[i + 1][p], f[i][j] + (mp[s[i + 1]] != k));
        }
      }

    int res = 0x3f3f3f3f;
    for (int i = 0; i <= idx; i++) res = min(res, f[m][i]);

    if (res == 0x3f3f3f3f) res = -1;
    printf("Case %d: %d\n", T++, res);
  }
}

时间复杂度 O ( l s N ) O(l_sN) O(lsN),空间 O ( N ) O(N) O(N)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值