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

           DNA Repair

考虑只由’A’,’G’,’C’,’T’四种字符组成的 DNA字符串。给定一个原字符串 S,和 n个禁止模式字符串
p1,p2,..,pn。请修改字符串 S,使得其中不包含任何禁止模式。
每次修改操作只能将 S中的某个字符修改为其他字符。
如果不存在这样的修改,请输出 -1,否则,输出所需的最少修改回数。

思路:
这个问题看起来与前面的问题很相似。所以我们考虑从左向右修改,依然以已修改的位置和对应的后缀为状态进行动态规划。只不过与前面不同的是,这里禁止出现的字符串不只一个,而是有多个。那么应该用哪些状态来表示后缀才好呢?
实际上,这并不难。在前面的问题中,我们取字符串S的所有前缀为状态,这里也只要取所有字符串Pi的所有前缀为状态就好了。不过需要注意的是,我们可能从不同的字符串得到相同的前缀。譬如说,“AA”既是“AAA”的前缀,也是“AAG”的前缀。在“AA”后面添加‘A’可以得到”AAA“,添加’G’则会得到”AAG“。因此,需要把像”AA“这样的多个字符串的公共前缀作为同一个状态进行正确的处理。
与前面的问题一样,下面的代码中,首先是预处理出状态及其转移关系,然后再进行动态规划。预处理的复杂度O(n^2*l^2+n*l^3log(nl)),动态规划的复杂度是O(nl|S|),其中l表示Pi的最大长度

/*
DNA Repair
考虑只由'A','G','C','T'四种字符组成的 DNA字符串。给定一个原字符串 S,和 n个禁止模式字符串
p1,p2,..,pn。请修改字符串 S,使得其中不包含任何禁止模式。
每次修改操作只能将 S中的某个字符修改为其他字符。
如果不存在这样的修改,请输出 -1,否则,输出所需的最少修改回数。 
*/
#include<iostream>
#include<string.h>
#include<vector>
#include<algorithm>
using namespace std; 
const char *AGCT = "AGCT"; 
const int MAX_N = 100;
const int MAX_STATE = 1005;
const int MAX_LEN_S = 1005;
const int INF = 99999999;
//输入 
int N;
string S,P[MAX_N]; 
//预处理得到的数据
int next[MAX_STATE][4];//添加某个字符后转移得到的状态
bool ng[MAX_STATE];//是否是禁止转移到的状态
int dp[MAX_LEN_S + 1][MAX_STATE]; 
void solve(){
    //首先枚举出所有的字符串前缀
    vector<string> pfx;

    for(int i=0;i<N;i++){
        for(int j=0;j<=P[i].length();j++){
            pfx.push_back(P[i].substr(0,j)); 
        } 
    } 

    //排序并去重
    sort(pfx.begin(),pfx.end());
    pfx.erase(unique(pfx.begin(),pfx.end()),pfx.end());
    int K = pfx.size();

    //计算各个状态的相关信息
    for(int i=0;i<K;i++){
        //如果后缀和禁止模式匹配的话,就是禁止转移到的状态
        ng[i] = false;
        for(int j=0;j<N;j++){
            ng[i] |= P[j].length() <= pfx[i].length() 
                && pfx[i].substr(pfx[i].length() - P[j].length(),P[j].length()) == P[j];  
        }
        for(int j=0;j<4;j++){
            //添加一个字符后得到的字符串
            string s = pfx[i] + AGCT[j];
            //反复删除第一个字符,直到等于某个状态的字符串,该状态就是状态转移到的状态
            int k;
            for(;;){
                k = lower_bound(pfx.begin(),pfx.end(),s) - pfx.begin();
                if(k < K && pfx[k] == s)
                    break;
                s = s.substr(1); 
            } 
            next[i][j] = k; 
        } 
    } 

    //动态规划的边界初值
    dp[0][0] = 1;
    for(int i=0;i<K;i++)
        dp[0][i] = 0;
    //动态规划
    for(int t=0;t<S.length();t++){
        for(int i=0;i<K;i++)
            dp[t+1][i] = INF;
        for(int i=0;i<K;i++){
            if(ng[i])
                continue;
            for(int j=0;j<4;j++){
                int k = next[i][j];
                dp[t+1][k] = min(dp[t+1][k],dp[t][i] + (S[t] == AGCT[j] ? 0 : 1)); 
            } 
        } 
    } 

    int ans = INF;
    for(int i=0;i<K;++i){
        if(ng[i])
            continue;
        ans = min(ans,dp[S.length()][i]); 
    } 
    if(ans == INF)
        puts("-1");
    else
        printf("%d\n",ans); 
} 
int main(){
    cin>>S;
    cin>>N;
    for(int i=0;i<N;i++){
        cin>>P[i]; 
    } 
    solve(); 
    return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值