POJ 4899 Hero meet devil 【DP套DP[预处理]】

POJ4899入口

题意:一段DNA序列S,只包含字符ATGC,长度不超过15,求有多少种长度为m的DNA序列与s的最长公共子序列长度为0~n。

分析:DP,开始用dp[i][s]表示在T串中已排列i个字符,且与S串匹配为状态s时的数量。但是当此时枚举T串的下一个(i+1)字符时。状态怎么转移呢。我开始想的是在s串最后一个字符之后开始匹配。但是这有一个问题(比如设S串为GTC,XXCGTC(T串依次匹配)),还可能会重复计算,然后参考啦大佬博客。需要对每个状态进行预处理后,找出每个状态枚举没一个字符后的下一个最优状态。注意最优(即使lcs是一样的,位置更靠前也是最优)。

(每写一道经典题,要多多思考,不然白做啦)

ACcode


#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int INF=0x3f3f3f3f;
const int N=1001000;
typedef long long LL;
const LL MOD=1e9+7;
#define next NNNNNNNNN
LL ans[18],dp[2][1<<15];
int n,m,pre[20],lcs[20],next[1<<15][4],t;
char ss[20],ch[]="ATCG";
void init() //预处理过程
{
    for(int s=0;s<t;s++)
    {
        pre[0]=0;
        for(int i=1;i<=n;i++)pre[i]=pre[i-1]+(1&(s>>i-1));
        for(int l=0;l<4;l++)
        {
          for(int j=1;j<=n;j++)
          {
            if(ch[l]==ss[j])
                lcs[j]=pre[j-1]+1;
            else
                lcs[j]=max(lcs[j-1],pre[j]);
           }
            int &res=next[s][l]=0;
          for(int j=1;j<=n;j++)
              if(lcs[j]!=lcs[j-1])
              res|=(1<<(j-1));
        }
    }
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf(" %s%d",ss+1,&m);
        n=strlen(ss+1);

        t=1<<n;
        init();
        int x=0;
        memset(dp[0],0,sizeof(dp[0]));
        dp[0][0]=1;
        for(int i=0;i<m;i++)
        {   memset(dp[x^1],0,sizeof(dp[x^1]));
            for(int s=0;s<t;s++)
        {
            if(dp[x][s]==0)continue;
            for(int l=0;l<4;l++)
            {
                LL &res=dp[x^1][next[s][l]];
                res=(res+dp[x][s])%MOD;
            }
        }
        x^=1;
        }
        memset(ans,0,sizeof(ans));
        int cnt;
        for(int s=0;s<t;s++)
        {
            cnt=__builtin_popcount(s);
            ans[cnt]=(ans[cnt]+dp[x][s])%MOD;
        }
        for(int i=0;i<=n;i++)
        {
            printf("%lld\n",ans[i]);
        }
    }
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值