字符串上的动态规划算法--------单字符串的情况

            禁止字符串

考虑只由‘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;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值