P4591 [TJOI2018] 碱基序列,详细题解
一道“字符串hash + dp”的好题
一.题意简述
题面理解起来有点困难,翻译一下题面
给定 DNA 串 \(s\) 和 \(k\) 组字符串,从每组恰好选一个字符串,按组顺序拼接。要求拼接结果是 \(s\) 的子串,求所有选择方案在 \(s\) 中出现的总次数(同一方案能在多个位置出现会分别计数)。
答案对 \(10^9+7\) 取模。
数据范围: \(1\le k\le 100,1\le |s|\le 10000,1\le a_i\le 10\)
二.思路
由于是字符串之间的匹配,数据范围不小,所以我们选择使用 “字符串hash” 的方法来 \(O(1)\) 高效判断。
我们不难发现:当处理到第 \(i\) 组碱基序列时,它对答案的贡献可以通过 \(i-1\) 组碱基序列转移而来,考虑 dp
1.状态定义
我们定义 \(dp[i][r]\) 表示处理完第 \(i\) 组碱基序列后,拼接结果恰好结束在 \(s\) 的第 \(r\) 个位置的方案数
- \(i\) 表示已经处理了几组
- \(r\) 表示当前最后一个字符在 \(s\) 中的位置 \((1\sim len)\)
- 含义是:前 \(i\) 组选出的字符串收尾相邻地拼接起来后,其最后一个字符落在 \(s[r]\)
2.初始化
for(int i=0;i<=10000;i++)dp[0][i]=1;
dp[0][r]=1表示:还没选任何字符串时,可以从 s 的任意位置之后开始
如果没有初始化,第一段就只能从 s 的开头开始匹配,得到的是前缀匹配而非任意位置匹配
3.转移
for(int l=1,r=lx;l<=len and r<=len;r++,l++){
int key=(hs[r]+mod-hs[l-1]*mi[r-l+1]%mod)%mod;
if (key!=tar)continue;
dp[i][r]+=dp[i-1][l-1];
dp[i][r]%=mod;
}
核心思想是拼接:
- 前
i-1组拼好后,最后一个字符落在s[l-1] - 第
i组选出的串x恰好等于s[l...r] - 那么前
i组拼好后就落在s[r]
4.答案统计
for(int i=1;i<=len;i++){
ans+=dp[k][i]%mod;
}
处理完所有 k 组碱基序列后,把结束在 s 各个位置的方案数都加起来,别忘了对 \(10^9+7\) 取模
三.实现代码
题目传送门
没想到这竟然是道紫题
#include <bits/stdc++.h>
using namespace std;
#define int unsigned long long
const int mod=1e9+7;
const int N=1e4+5;
int k,hs[N],mi[N],dp[105][N],ans;
string s;
signed main(){
for(int i=0;i<=10000;i++)dp[0][i]=1;
mi[0]=1;
cin>>k>>s;
int len=s.size();
s=" "+s;
for(int i=1;i<=len;i++){
mi[i]=mi[i-1]*127%mod;
hs[i]=(hs[i-1]*127+s[i])%mod;
}
for(int i=1;i<=k;i++){
int a;
cin>>a;
for(int j=1;j<=a;j++){
string x;
cin>>x;
int tar=0,lx=x.size();
x=" "+x;
for(int u=1;u<=lx;u++){
tar=(tar*127+x[u])%mod;
}
for(int l=1,r=lx;l<=len and r<=len;r++,l++){
int key=(hs[r]+mod-hs[l-1]*mi[r-l+1]%mod)%mod;
if (key!=tar)continue;
dp[i][r]+=dp[i-1][l-1];
dp[i][r]%=mod;
}
}
}
for(int i=1;i<=len;i++){
ans+=dp[k][i]%mod;
}
cout<<ans%mod;
return 0;
}

浙公网安备 33010602011771号