[Codeforces]86C Genetic engineering AC自动机 + DP

  这道题之前YYR出过, 最近重做AC自动机于是又来旧题重做一下~

  题目大意就是说给你m个字符串, 然后让你统计长度为n的并且由这m个字符串中的一些字符串紧凑拼成的字符串有多少个. 什么叫紧凑拼成? 可以理解为这个字符串包含这m个字符串中的一些, 相邻之间可以互相重叠但是不能有间隔——也就是说这个字符串的每一个字符一定要至少被给定的m个字符串之一覆盖.

  这种计数方案数肯定要想到dp. 由于可以重叠很类似于跳fail所以我们又要想到AC自动机. 将m个字符串建成一个AC自动机, 设dp[i][j][k]表示当前长度为i的走到AC、自动机上的第j号节点当前末尾还有k个没有被覆盖. 那么采用刷表式(可以发现刷表式好写)dp, 枚举当前j的儿子, 如果j的儿子son是一个结尾节点的话, 那么就可以覆盖他最大覆盖长度cnt[son]这么多的字符. 那么当cnt[son] >= k + 1的时候, 就可以由dp[i][j][k]转移到dp[i + 1][son][0]. 为什么要 >= 呢? 因为当前到了son还剩k + 1个没被覆盖, 如果覆盖不到的话中间就有些没被覆盖, 就不符合题意了. 所以当 < k + 1的时候就转移到f[i + 1][son][k + 1].

  注意cnt数组要从fail那里转移过来. 因为有些节点可能不是结尾节点, 但他的fail链上可能有个是, 那么根据fail定义那个结尾节点就是他的后缀, 那么他也包含了这个字符串.

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1005;
const int mod = 1e9 + 9;
char ss[11];
int n, m, tot, ans;
int mean[maxn], f[maxn][maxn][11];
int c[maxn][5], fail[maxn], cnt[maxn];
inline void insert() {
    int p = 0;
    int len = strlen(ss);
    for (int i = 0; i < len; ++ i) {
        int idx = mean[(int)ss[i]];
        if (!c[p][idx]) c[p][idx] = ++ tot;
        p = c[p][idx];
    }
    cnt[p] = len;
}
queue<int> q;
inline void bfs() {
    for (int i = 0; i < 4; ++ i)
        if (c[0][i]) q.push(c[0][i]);
    while (!q.empty()) {
        int u = q.front(); q.pop();
        for (int i = 0; i < 4; ++ i) {
            int v = c[u][i];
            if (!v) {c[u][i] = c[fail[u]][i]; continue;}
            fail[v] = c[fail[u]][i];
            cnt[v] = max(cnt[v], cnt[fail[v]]);
            q.push(v);
        }
    }
}
inline void add(int &a, int b) {
    a += b;
    a = (a >= mod) ? a - mod : a;
}
inline void dp() {
    f[0][0][0] = 1;
    for (int i = 0; i < n; ++ i)
        for (int j = 0; j <= tot; ++ j)
            for (int k = 0; k < 10; ++ k)
                if (f[i][j][k]) 
                    for (int p = 0; p < 4; ++ p) {
                        int son = c[j][p];
                        if (cnt[son] >= k + 1) add(f[i + 1][son][0], f[i][j][k]);
                        else add(f[i + 1][son][k + 1], f[i][j][k]);
                    }
    for (int i = 0; i <= tot; ++ i)
        add(ans, f[n][i][0]);
}
int main() {
    mean['A'] = 0, mean['G'] = 1, mean['C'] = 2, mean['T'] = 3;
    scanf("%d%d", &n, &m);
    for (int i = 0; i < m; ++ i) {
        scanf("%s", ss);
        insert();
    }
    bfs(), dp();
    printf("%d\n", ans); 
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值