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;
}

浙公网安备 33010602011771号