加载中...

2026蓝桥杯省赛 C++ A 基因研究 (AC自动机/KMP自动机 + dp)

P16231

做法1:AC自动机 + dp

AC 自动机对于本题的的最大作用在于可以很快地找到当前位置失配时,可以匹配的最长子后缀位置所表示的状态。即使是多模式串匹配算法,当只有一个模式串时,AC自动机就相当于只在自己的后缀里找最长子后缀,完美符合我们进行状态转移的需求。

#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define fr first
#define se second
#define endl '\n'
#define pb push_back
//#define int long long 
typedef long long ll;
typedef __int128 LL;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<ll,int> pli;

const int mod = 998244353;
map<char,int> mp;

int ch[3005][4], tot;
int fail[3005];
void insert(string s){
    int p = 0;
    for(int i = 0; i < s.size(); i ++){
        int j = mp[s[i]];
        if(!ch[p][j]) ch[p][j] = ++tot;
        p = ch[p][j];
    }
}
void build(){
    queue<int> q;
    for(int i = 0; i < 4; i ++){
        if(ch[0][i]){
            q.push(ch[0][i]);
        }
    }
    while(q.size()){
        int u = q.front();
        q.pop();
        for(int c = 0; c < 4; c ++){
            if(ch[u][c]){
                fail[ch[u][c]] = ch[fail[u]][c];
                q.push(ch[u][c]);
            }
            else{
                ch[u][c] = ch[fail[u]][c];
            }
        }
    }
}

int n, m;
string s;
int p[3005][4];
ll dp[3005][3005]; // dp[i][u]:考虑基因序列的前i个字符,当前序列的最长后缀是AC自动机中的结点u所表示的前缀,概率

void solve()
{
    cin >> n >> m;
    cin >> s;
    mp['A'] = 0; mp['T'] = 1; mp['G'] = 2; mp['C'] = 3;
    insert(s);
    int ed = tot; // 终止结点编号,即匹配到该结点时,整个序列中包含易感序列
    build();
    for(int i = 0; i < 4; i ++) ch[ed][i] = ed; // 终止状态的所有后继状态均设置成自己,这样整个序列中包含了易感序列时,后继状态可以正确递推下去
    for(int i = 1; i <= n; i ++){
        for(int j = 0; j < 4; j ++){
            cin >> p[i][j];
        }
    }
    dp[0][0] = 1;
    for(int i = 1; i <= n; i ++){
        for(int j = 0; j <= tot; j ++){ // 枚举 AC 自动机的所有结点,相当于枚举 当前串的后缀 对应 易感序列前缀 的所有可能性
            for(int c = 0; c < 4; c ++){ // 枚举所有可能的转移,利用AC自动机关于 fail 指针路径压缩的性质,ch[j][c] 直接到达可匹配的最长后缀位置
                dp[i][ch[j][c]] += dp[i - 1][j] * p[i][c] % mod;  
                dp[i][ch[j][c]] %= mod;
            }
        }
    }
    cout << dp[n][ed] << "\n";
}


signed main()
{
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    // int T=1; cin>>T; while(T--)
    solve();
    return 0;
}

做法2:KMP自动机 + dp

#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define fr first
#define se second
#define endl '\n'
#define pb push_back
#define int long long 
typedef long long ll;
typedef __int128 LL;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<ll,int> pli;

const int mod = 998244353;

map<char,int> mp;
int n, m;
string s;
ll dp[3005][3005]; // dp[i][j]: 考虑基因序列的前i位,易感序列恰好最多匹配到第j位,概率
int p[3005][4];

int ne[3005];
int to[3005][4]; // to[i][c]:对于原模式串,在第i个位置往后加一个字符c,所有后缀中匹配到原串的最长前缀长度

void solve()
{
    cin >> n >> m;
    cin >> s;
    s = " " + s;
    for(int i = 1; i <= n; i ++){
        for(int j = 0; j < 4; j ++){
            cin >> p[i][j];
        }
    }

    ne[1] = 0;
    for(int i = 2, j = 0; i <= m; i ++){
        while(j && s[i] != s[j + 1]) j = ne[j];
        if(s[i] == s[j + 1]) j ++;
        ne[i] = j;
    }
    array<char,4> t = {'A', 'T', 'G', 'C'};
    // build KMP automaton
    for(int i = 0; i < m; i ++){
        for(int j = 0; j < 4; j ++){
            if(s[i + 1] == t[j]) to[i][j] = i + 1;
            else to[i][j] = to[ne[i]][j];
        }
    }
    for(int j = 0; j < 4; j ++){ // 终止状态仍转移到自己
        to[m][j] = m;
    }

    ll ans = 0;
    dp[0][0] = 1;
    for(int i = 0; i < n; i ++){
        for(int c = 0; c < 4; c ++){ // 往第i+1个位置填字符c
            for(int j = 0; j <= min(i, m); j ++){
                dp[i + 1][to[j][c]] += dp[i][j] * p[i + 1][c] % mod;
                dp[i + 1][to[j][c]] %= mod;
            }
        }
    }
    cout << dp[n][m] << "\n";
}


signed main()
{
//  freopen("in.txt", "rt", stdin);
//  freopen("out.txt", "wt", stdout);
    ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
    // int T=1; cin>>T; while(T--)
    solve();
    return 0;
}
posted @ 2026-04-14 23:01  小橘奏  阅读(30)  评论(0)    收藏  举报