acwing1053. 修复DNA(AC 自动机+dp)

题意

  1. 给定 n 个子串 ti 四个字符,和一个主串 s,所有字符串仅包含 A,G,C,T,问最少需要修改几个字符,才使 s 中不包含任意一个 ti 字符串。
  2. n < 50, |s|<=1000, |ti<=20|.

思路

  1. 这是 y 总讲的状态机模型,我们可以先设出 dp 方程:dp [i][j] 表示匹配 i 个字符,且已经匹配到自动机树中的编号为 j 的节点位置,
  2. 那么我们考虑状态转移:如果当前我么已经求出 dp [i][j] 的话,我们可以枚举长度为第 i+1 个字符选择 A,G,C, T 中的一个字符设为 x,进行状态递推,dp [i][j] 是可以递推出 dp [i+1][k] 这个状态的话 如果 tr [j][x] == k, 并且要求第 k 个位置没有单词结尾,那么这就是一个合法的状态转移。
  3. 补充:自动机中节点个数非常少,且待匹配的字符串非常的长的话,我们可以考虑用 矩阵快速幂 优化状态转移。

代码

#include <bits/stdc++.h>
using namespace std;
#define db  double
#define ll  long long
#define Pir pair<int, int>
#define fi  first
#define se  second
#define pb  push_back
#define m_p make_pair
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
/*==========ACMer===========*/
const int N = 1005;
int n;
char s[N];
int tr[N][4], fa[N], mk[N], idx;
int dp[N][N];
int id[150];

void pre_do()
{
    id['A'] = 0;
    id['G'] = 1;
    id['C'] = 2;
    id['T'] = 3;
}

void insert()
{
    int u = 0;
    for (int i = 0; s[i]; i ++) {
        if (tr[u][id[s[i]]] == 0) tr[u][id[s[i]]] = ++ idx;
        u = tr[u][id[s[i]]];
    }

    mk[u] = 1;
}

void build()
{
    queue<int> q;
    for (int i = 0; i < 4; i ++) {
        if (tr[0][i]) q.push(tr[0][i]);
    }

    while (q.size())
    {
        int u = q.front(); q.pop();
        if (mk[fa[u]]) mk[u] = 1;

        for (int i = 0; i < 4; i ++) {
            if (tr[u][i])
                fa[tr[u][i]] = tr[fa[u]][i], q.push(tr[u][i]);
            else
                tr[u][i] = tr[fa[u]][i];
        }
    }
}

void init()
{
    memset(tr, 0, sizeof tr);
    memset(fa, 0, sizeof fa);
    memset(mk, 0, sizeof mk);
    idx = 0;

}

int main()
{
    pre_do();
    int cas = 1;
    while (scanf("%d", &n) && n)
    {
        init();
        for (int i = 1; i <= n; i ++) {
            scanf("%s", s);
            insert();
        }
        scanf("%s", s);
        int m = strlen(s);

        build();

        memset(dp, inf, sizeof dp);
        dp[0][0] = 0;
        for (int i = 0; i < m; i ++) {
            for (int j = 0; j < idx; j ++) {
                for (int k = 0; k < 4; k ++) {
                    int l = tr[j][k];
                    if (mk[j] || mk[l] || dp[i][j] == inf) continue;
                    dp[i + 1][l] = min(dp[i + 1][l], dp[i][j] + (id[s[i]] != k));
                }
            }
        }

        int ans = inf;
        for (int i = 0; i < idx; i ++)
            ans = min(ans, dp[m][i]);

        printf("Case %d: ", cas ++);
        if (ans == inf)
            printf("-1\n");
        else
            printf("%d\n", ans);
    }

    return 0;
}

上一篇:可持久化线段树


下一篇:html中的

标签