设计密码 修复DNA GT考试

 

 铅笔佬题解 写的真的超好

f[i][j] i是表示要构造的文本串枚举到了第i个字母 j是表示0~m个状态  这个状态是指与模式串匹配了多少字符 也就是0~i文本串后缀与模式串0~j前缀是相同的

枚举第i+1个字符a~z 然后就包括了这个字符后 文本串0~i+1后缀能匹配模式串多少前缀了 就由u=j来往前跳到了另一个匹配状态 所以就是f[i+1][u]+=f[i][j] 因为原来就是第i个字符就匹配到了模式串的j个字符 然后枚举一次第i+1字符 状态变为u 就是由ij这个状态转移过来的

同时 在i固定下 是什么状态也是可以枚举的 j从0~m-1枚举来的

#include<iostream>
using namespace std;
const int mod=1e9+7;
int ne[52],f[52][52];
int main(){
    int n,i,j;
    string s;
    cin>>n>>s;
    s=" "+s;
    int m=s.size();
    m--;
    for(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;
    }
    f[0][0]=1;
    for(i=0;i<n;i++){  //已经排好i位了
        for(j=0;j<m;j++){
            for(char c='a';c<='z';c++){
                int u=j;
                while(u&&c!=s[u+1]) u=ne[u];
                if(s[u+1]==c) u++;
                if(u<m) f[i+1][u]=(f[i+1][u]+f[i][j])%mod;
            }
        }
    }
    int res=0;
    for(i=0;i<m;i++) res=(res+f[n][i])%mod;
    cout<<res<<endl;
}

 

 输入

 2
AAA
AAG
AAAG    
2
A
TG
TGAATG
4
A
G
C
T
AGT
0

几乎同样的思路 就是注意好初始化与更新方式 是取min 不用担心啥只修改了一部分 会不会没修改完全就取值更小了 其实这个第一个for就是可以作出这个限制 发现每一次只要不满足 那我就一定不会用它来更新i+1的状态 也就是说枚举到i+1填k这个字符的这个状态完全没有用 然后是把所有的每i位可以填的值枚举出来 没有管原串是怎么样 其实就是看我是这个字符 那原串相不相同 不相同就要霸道的一定给我修改 然后取min 这样最后其实就递推i得到了结果

#include<iostream>
#include<cstring>
using namespace std;
const int N=1e4;
int tr[N][27],cnt[N],ne[N],id,q[N],f[1002][N];
string s;
int get(char a){
    if(a=='A') return 0;
    if(a=='G') return 1;
    if(a=='C') return 2;
    return 3;
}
void insert(){
    int i,p=0;
    for(i=0;i<s.size();i++){
        int t=get(s[i]);
        if(!tr[p][t]) tr[p][t]=++id;
        p=tr[p][t];
    }
    cnt[p]++;
}
void build(){
    int hh=0,tt=-1,i;
    for(i=0;i<4;i++){
        if(tr[0][i]) q[++tt]=tr[0][i];
    }
    while(hh<=tt){
        int t=q[hh++];
        for(i=0;i<26;i++){
            int p=tr[t][i];
            if(!p) tr[t][i]=tr[ne[t]][i];
            else{
                ne[p]=tr[ne[t]][i];
                q[++tt]=p;
//因为后面是直接看tr编号的cnt值 所以如果是有以这个字符大串的后缀(不是这个字符串)为不合法序列的话 那要将ne的cnt赋给它
                cnt[p]|=cnt[ne[p]];
            }
        }
    }
}
int TT;
int main(){
    int n,i,j,k;
    while(cin>>n,n){
        memset(tr,0,sizeof tr);
        memset(ne,0,sizeof ne);
        memset(cnt,0,sizeof cnt);
        id=0;
        for(i=0;i<n;i++) {cin>>s;insert();}
        build();
        cin>>s;
        s=" "+s;
        int m=s.size();
        m--;
        int type;
        memset(f,0x3f,sizeof f);
        f[0][0]=0;
        for(i=0;i<m;i++){  //前i个字符都匹配好了(修改好了) 
            for(j=0;j<=id;j++){ //匹配到了tr中的j编号
                for(k=0;k<4;k++){ //枚举AGCT
                    if(get(s[i+1])==k) type=0;else type=1;  //type代表要不要修改 
                    int p=tr[j][k];  //直接tr就是编号或者为跳到的地方 如果说
                    if(!cnt[p]) f[i+1][p]=min(f[i][j]+type,f[i+1][p]);
                }
            }
        }
        int res=0x3f3f3f3f;
        //可以是全部0~id取最小 因为只要cnt不为0的不合法的地方不可能更新到 一直都是0x3f3f3f3f
        for(i=0;i<=id;i++) res=min(res,f[m][i]);
        if(res==0x3f3f3f3f) res=-1;
        printf("Case %d: %d\n",++TT,res);
    }
}

 

 

很多dp问题都可以这样做 就是前一位与这一位这个递推关系是不变的 那都可以表示成矩阵乘法来做

综合了kmp dp递推 状态机 矩阵乘法知识

#include<iostream>
#include<cstring>
using namespace std;
const int N=25;
int mod,n,m;
int ne[N],a[N][N];
string s;
void get_next(){
    int i,j;
    for(i=2,j=0;i<s.size();i++){
        while(j&&s[i]!=s[j+1]) j=ne[j];
        if(s[i]==s[j+1]) j++;
        ne[i]=j;
    }
}
void mul(int c[][N],int a[][N],int b[][N]){
    static int t[N][N];   //可以省去每次调用都开空间的时间
    memset(t,0,sizeof t);
    int i,j,k;
    for(i=0;i<m;i++)
        for(j=0;j<m;j++)
            for(k=0;k<m;k++)    //二维*二维
                t[i][j]=(t[i][j]+a[i][k]*b[k][j])%mod;
    memcpy(c,t,sizeof t);
    
}
int qmi(int k){
    int f[N][N]={1};  //初始化为1种方案
    while(k){
        if(k&1) mul(f,f,a);
        mul(a,a,a);
        k>>=1;
    }
    int res=0;
    for(int i=0;i<m;i++) res=(res+f[0][i])%mod;
    return res;
}
int main(){
    int i;
    cin>>n>>m>>mod;
    cin>>s;
    s=" "+s;
    get_next();
    //得到系数矩阵
    for(i=0;i<m;i++){  //匹配了i个数
        for(char j='0';j<='9';j++){
            int p=i;
            while(p&&s[p+1]!=j) p=ne[p];
            if(s[p+1]==j) p++;
            if(p<m) a[i][p]++;  //可以好好模拟下为啥是这两个下标
        }  // 因为这个时候i不变 也就是行不变 第二维在变的是列 所以就是更新匹配了i个字符时 0~m-1的状态跳过去的系数为列
    }
    cout<<qmi(n)<<endl;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值