[BZOJ 3864][HDU 4899]Hero meet devil(DP套DP)

59 篇文章 0 订阅
15 篇文章 0 订阅

题目链接:

(1)http://www.lydsy.com/JudgeOnline/problem.php?id=3864
(2)http://acm.hdu.edu.cn/showproblem.php?pid=4899

题目大意:

给你一个只由AGCT组成的字符串 S(|S|15) ,对于每个 0i|S| ,问
有多少个只由AGCT组成的长度为 m(1m1000) 的字符串T,使得LCS (S,T)=i ?
LCS就是最长公共子序列。

题目来源:

2014 Multi-University Training Contest 4, by WJMZBMR.

思路:

观察LCS的DP过程:
if S[i]==T[j] dp[i][j]=dp[i-1][j-1]+1
else dp[i][j]=max{dp[i-1][j],dp[i][j-1]}
可以发现在每次决策时,dp[i][j]要么不会变,要么会加上1
那么我们可以用一个二进制数 S 来表示这个过程,S中第 i 位为0表示在 T 串的第i位时没有加 11 表示加了 1
可以用DP套DP来求解。枚举LCS的DP值转移状态S dp[i]= 在DP值转移状态为 S 时,DP到第i位时的DP值。然后求出 ans[] 数组, ans[S][ch]= 在DP值转移状态为 S 的T串后面加上一个字母ch后的DP值转移状态,求出 ans[] 数组需要依赖一个 dp1[] 数组, dp1[i]=T 加入了字母 ch 后的新的DP数组, dp1[] 数组可以像普通的LCS一样DP求出。
然后求 f[][] 数组, f[len][S]= 长度为 len ,dp值转移状态为 S 的不同的T串个数, f[][] 数组可以由 ans[][] 数组求出。
最终与 S 串LCS长度为i的不同的 T 串个数为(f[lenT][s]),s1i

代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 1000
#define MOD 1000000007

using namespace std;

typedef long long int LL;

LL f[2][MAXN]; //滚动数组优化
char s[MAXN]; //S串
LL answer[MAXN]; //最终answer[i]=与S串的LCS为i的不同的T串个数
int dp[MAXN],dp1[MAXN]; //dp[i]=在T串中DP到第i位时的DP值(LCS长度)
char dna[]={'A','T','G','C'};
LL ans[MAXN][5]; //ans[S][ch]=在DP转移状态为S的T串后面加上一个字母ch后的DP状态

int main()
{
    int T,m;
    scanf("%d",&T);
    while(T--)
    {
        memset(ans,0,sizeof(ans));
        memset(f,0,sizeof(f));
        memset(answer,0,sizeof(answer));
        scanf("%s",s);
        scanf("%d",&m);
        int len=strlen(s);
        int maxs=1<<len; //最大二进制集合大小
        for(int S=0;S<maxs;S++) //枚举DP过程中的状态转移情况S
        {
            dp[0]=0;
            int tmp=S;
            int cnt=1;
            for(int i=0;i<len;i++) //这里和正常的DP一样,S中的第i位为1表示两个串的第i位相同,否则是不相同
            {
                if(tmp&1)
                    dp[cnt]=dp[cnt-1]+1;
                else
                    dp[cnt]=dp[cnt-1];
                cnt++;
                tmp>>=1;
            }
            for(int i=0;i<4;i++) //计算加上A、T、G、C中的一个字母后的
            {
                dp1[0]=0;
                for(int j=1;j<=len;j++) //枚举dp数组中到了第j位
                {
                    if(s[j-1]==dna[i])
                        dp1[j]=dp[j-1]+1;
                    else
                        dp1[j]=max(dp1[j-1],dp[j]);
                }
                for(int j=len;j>=1;j--) //得到加入字母i后的DP过程状态
                {
                    ans[S][i]<<=1;
                    if(dp1[j]>dp[j-1]) //在第j位时S和T的LCS加入了新字母
                        ans[S][i]+=1;
                }
            }
        }
        //f数组使用滚动数组优化,f[i&1]为当前使用的维度,f[(i+1)&1]为下一次DP使用的维度
        f[0][0]=1;
        for(int i=0;i<m;i++) //枚举到了T串中的第i位
        {
            memset(f[(i+1)&1],0,f[(i+1)&1]);
            for(int j=0;j<maxs;j++) //枚举当前的DP状态转移状态
                for(int k=0;k<4;k++) //T串的第i+1位加入了字母k
                {
                    f[(i+1)&1][ans[j][k]]+=f[i&1][j];
                    if(f[(i+1)&1][ans[j][k]]>MOD) //取模的常数优化
                        f[(i+1)&1][ans[j][k]]-=MOD;
                }
        }
        memset(answer,0,sizeof(answer));
        for(int i=0;i<maxs;i++) //枚举DP状态i
        {
            int cnt=0; //最终cnt=LCS的长度
            int tmp=i;
            while(tmp)
            {
                if(tmp&1)
                    cnt++;
                tmp>>=1;
            }
            answer[cnt]+=dp[m&1][i];
            if(answer[cnt]>MOD) answer[cnt]-=MOD;
        }
        for(int i=0;i<=len;i++)
            printf("%lld\n",answer[i]);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值