禁止字符串
考虑只由‘A’,’G’,’C’,’T’四种字符组成的DNA字符串。给定一个长度为 k的字符串 S。请计算长度恰好为 n且不包含S的字符串的个数。输出个数mod 10009后的结果。
首先,我们来考虑生成所有的满足条件的字符串这一直观的解法。字符串的个数可能高达4^n,这显然是行不通的。接下来,与其在生成字符串后再判断它是否包含 S,不如在穷竭搜索中每在末尾加一个字符,都确保其最后k个字符不等于 S。可以发现最后 k个字符之前的字符对以后的搜索都无影响。(符合无后向性,不懂可以看看我写的动态规划一般解法的文章)所以我们可以以剩余字符的个数和最后k-1个字符为状态进行动态规划。这样的状态数依然高达4^(k-1)。不过事实上可以将其中的许多状态看作是等价的,从而大大减少所需的状态数。首先我们以 S等于“ATCG”为例进行分析。动态规划的状态是字符的个数和最后的3个字符。
譬如说,我们可以将最后三个字符为“TTA”的状态和“CCA”的状态看作是等价的。这是因为‘A’是S的第一个字符,所以“TT”或“CC”这两个字符对以后是否出现S并无影响。同理可知,对于以‘A’结尾的状态,‘A’前面的字符是什么并不重要。由此可知,我们可以将“*A”(‘’代表任意一个字符)看作一个状态。
以‘T’结尾的状态又如何呢?’T’是‘S’的第二个字符。因此要使该字符对 S的出现有影响,其前面的字符必须是’A’,并且同之前分析的一样,在前面的字符是什么并无影响。所以可以将”**AT“看作一个状态。而如果‘T’前面的字符不是’A‘的话,那么这个’T‘也可以无视。
综合以上分析,我们知道最初的4^3种状态,可以归纳为以下4种
“A”,”*AT”,”ATC”,其他
也就是把所生成字符串的后缀和S的前缀相匹配的长度作为状态。所以,状态总数只有k个。
接下来,我们来考虑一个更为复杂的例子,令S等于“ATCATCG”。此时,有个地方需要稍微注意一下。简单地像前面一样处理的话,将得到“ATCATC”和“**ATC”这两个状态。如果我们对 “***ATC”的前3个字符不加任何限制的话,那么就发生了以“ATCATC”结尾的状态,同时属于这两个状态这一奇怪现象。不过,要解决这一问题也很容易。最初我们引入‘’时,是用于代替那些已知对以后是否出现是否出现S无影响的字符。对于“ATCATC”,如果下一个字符是‘G’的话,就得到了S,对S的出现是有影响的。所以“ATCATC”不应该属于“***ATC”。
将这个一般化就会发现,状态应该是所生成字符串的后缀和S的前缀相匹配的长度,只不过当有多种匹配时,应当选择最长的作为状态。
为了让动态规划算法部分更加高效,下面的程序预先处理出了从某个状态添加某个字符后的状态转移表,然后利用该完成动态规划。预处理部分的复杂度是O(k^3),动态规划的复杂度是O(kn)。
/*
禁止字符串
考虑只由'A','G','C','T'四种字符组成的 DNA字符串。给定一个长度为 k的字符串 S。
请计算长度恰好为 n且不包含 S的字符串的个数。输出个数 mod 10009后的结果
*/
#include<iostream>
#include<string.h>
#include<cstdio>
using namespace std;
const char *AGCT = "AGCT";
const int MOD = 10009;
const int MAX_K = 105;
const int MAX_N = 10005;
//输入
int N,K;
string S;
int next[MAX_K][4];//添加某个字符串后转移到的状态
int dp[MAX_N + 1][MAX_K];
void solve(){
//预处理
for(int i=0;i<K;i++){
for(int j=0;j<4;j++){
//在 S长度为 i的前缀后添加一个字符
string s = S.substr(0,i) + AGCT[j];
//反复删除第一个字符,直到成为 S的前缀
while(S.substr(0,s.length()) != s){
s = s.substr(1);
}
next[i][j] = s.length();
}
}
//动态规划边界的初值
dp[0][0] = 1;
for(int i=1;i<K;i++)
dp[0][i] = 0;
//动态规划
for(int t=0;t<N;t++){
for(int i=0;i<K;i++)
dp[t+1][i] = 0;
for(int i=0;i<K;i++){
for(int j=0;j<4;j++){
int ti = next[i][j];
if(ti == K)
continue;//不允许出现 S
dp[t+1][ti] = (dp[t+1][ti] + dp[t][i]) % MOD;
}
}
}
int ans = 0;
for(int i=0;i<K;i++){
ans = (ans + dp[N][i]) % MOD;
}
printf("%d\n",ans);
}
int main(){
cin>>N>>K;
cin>>S;
solve();
return 0;
}