hdu4899 dp套dp

题意:只含字母ATGC,  给定一个S串,长度小于等于15,构造满足LCS(S,T)=X的T串,求这样的T串的个数,0<=X<=|S|

网上有一堆题解,但大多数都讲得根本让人无法理解


以下我给出易理解的题解:

一个经典的问题,给定S和T,求LCS(C,S)

我们利用经典的DP求解

DP[i][j]表示S[1...i]与T[1...j]的LCS

那么有转移方程:

1.DP[i][j]=max(DP[i-1][j],DP[i][j-1])   s.t. S[i]!=T[j]

2.DP[i][j]=DP[i-1][j-1]+1  s.t. S[i]==T[j]

首先可以证明DP[i][j]>=DP[g][h],如果i>=g&&j>=h(性质)

这是显然的,因为S[1...i]与T[1...j]可以构造出S[1...g]与T[1...h]的配对方案

所以转移方程2是很显然的,贪心让S[i]与T[j]配对,这样才能使LCS最大,否则让S[i]与T[k],k<j配对,那么DP[i][j]=DP[i-1][k-1]+1,,而DP[i-1][k-1]<=DP[i-1][j-1] (根据前面的性质),其他情况同理可证

所以贪心是对的


===========================     分割线   ==============================

现在来考虑这题

首先我们求得的方案数,必须保证S和T要达到LCS(S,T),即没有达到LCS(S,T)的T串不应该被计数

这个是比较棘手的,因为我们需要记录些关于LCS的信息


由于S串是已知的,那么我们来对T串DP

DP[j][k]表示T[1...j]与S达到LCS=k时,这样的T串的个数,貌似这个状态很抽象

你可以这么想,因为只有4个字母,那么T[1...j]最多有4^j个可能,对于每个可能,都可以计算与S串的LCS,然后我们把相同LCS的这样的T串统计在一起,这就是上面状态想要表述的,很显然DP[j][0]+DP[j][1]+...=4^j

然后我们考虑转移:

设S的长度为n,即S[1...n]

当T[j]==S[n]时,我们利用贪心让T[j]与S[n]配对,这样DP[j][k]由 T[1...j-1]与S[1...n-1]做匹配转移得到,这样就出问题了,因为DP数组表示的是与S串的匹配,这里出现了S的前缀,看来DP的定义还得扩充


扩展:DP[i][j][k]表示S[1...i]与T[1...j]的LCS=k的T串的个数

再来考虑转移:

当S[i]==T[j]时, DP[i][j][k]+=DP[i-1][j-1][k-1]

当S[i]!=T[j]时, DP[i][j][k]+=DP[i-1][j][k]+DP[i][j-1][k]-DP[i-1][j-1][k]

然而这样还是错的,比如S=“GT”

计算dp[2][1][0]的时候,我们枚举T[1],当T[1]='T'的时候,不转移是正确的,因为状态会变成dp[1][0][-1]

当T[1]='G'的时候,实际上LCS=1,而转移方程并没有往前比较S[i-1]字符的情况,于是就会产生错误的转移


这启发我们状态需要更复杂,真正的状态是这样的:

DP[j][Status]表示T[1...j]与S的状态为Status时T串的数量,那么Status是什么呢?

就是T[1...j]与S[1...1]的LCS=A1,且T[1...j]与S[1...2]的LCS=A2,......,且T[1...j]与S[1...n]的LCS=An,这个状态是相当完备的,实现往前比较的功能,因为这里存了T[1...j]与所有S的前缀的所有可能的LCS的组合

转移式显然的,因为给T加上一个字符后,状态必然还是在Status中,即DP[j][Status]->DP[j+1][NewStatus]

这样的Status状态集是完备的

然而这里遇到麻烦了,由于Ai<=min(i,j),最坏情况Status的所有状态是A1*A2*...*An,是n!, 这个实在太可怕!!!

但是我们发现一条性质Ai==A(i-1)||Ai==1+A(i-1)

这个很显然的,因为T[1...j]与S[1...i]的LCS最多比T[1...j]与S[1...i-1]的LCS大1

并且A1==0||A1==1

于是乎,我们想到可以用差分来表示状态(A1,A2,...,An)

差分后(A1-A0,A2-A1,...,An-A(n-1)),这里令A0=0

然后神奇的事发生了所有差分后的Ai-A(i-1)==0||Ai-A(i-1)==1,即状态是一个长度为n的01串

而n<=15,很显然状态压缩吧,于是Status瞬间降为2^n

并且通过差分序列我们可以还原A1,A2,...,An


关键是状态是怎么转移?

DP[j][Status]如何转化成DP[j+1][NewStatus]

首先我们需要解析二进制的Status,还原出A1,A2,...,An

然后我们要枚举T[j+1] {'A','T','G','C'}, 并且设新状态B1,B2,...,Bn

Bi>=Ai,这个显然因为T[1...j+1]与S[1...i]的LCS大于等于T[1...j]与S[1...i]的LCS

差别在于多出来的T[j+1]能否被S中的某个字母匹配

计算B1,令B1=A1,如果T[j+1]==B[1],贪心匹配,B1=max(B1,1+A0)

计算B2,令B2=max(B1,A2),如果T[j+1]==B[2],贪心匹配,B2=max(B2,1+A1)

...

计算Bi,令Bi=max(B(i-1),Ai),如果T[j+1]==B[i],贪心匹配,Bi=max(Bi,1+A(i-1))

...

其中Bi=max (B(i-1),Ai)的含义是什么呢? Bi=max(Bi,1+A(i-1))呢?

B(i)表示T[1...j+1]与S[1...i]的LCS

B(i-1)表示T[1...j+1]与S[1...i-1]的LCS,Ai表示T[1...j]与S[1...i]的LCS,1+A(i-1)表示T[1...j]与S[1...i-1]的LCS+1

有没有发现最经典的LCS转移方程:

dp(i,j)=max(dp(i,j-1),dp(i-1,j))  s.t. S[i]!=T[j]

dp(i,j)=dp(i-1,j-1)+1   s.t. S[i]==T[j]

只是转移方程中j被j+1替换而已


等等,发现了什么东西?在外面的大的一层DP求解Status时,又套了一层经典的LCS的DP

于是乎这玩意叫做DP套DP也不为过


贴代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;

const int mod=1e9+7;
int dp[2][1<<15],trans[1<<15][4];
char s[20];
char ch[]="ATGC";
void add(int &x,int y){
    x=(x+y)%mod;
}
void init(int n){ //预处理 Status->NewStatus
    for(int status=0;status<(1<<n);status++){
        int A[20]={0};
        for(int i=1;i<=n;i++)
            A[i]=A[i-1]+(status>>i-1&1); //状态是差分,所以要维护前缀和
        for(int j=0;j<4;j++){ //枚举{'A','T','G','C'}
            int B[20]={0};
            for(int i=1;i<=n;i++){ //维护B[i]
                B[i]=max(B[i-1],A[i]);
                if(ch[j]==s[i]) B[i]=max(B[i],A[i-1]+1);
            }
            int NewStatus=0;
            for(int i=1;i<=n;i++) //差分维护NewStatus
                NewStatus|=B[i]-B[i-1]<<i-1;
            trans[status][j]=NewStatus;
        }
    }
}
int main()
{
    int t,m;
    cin>>t;
    while(t--){
        cin>>s+1>>m;
        int n=strlen(s+1);
        init(n);
        memset(dp,0,sizeof dp);
        int l=0,r=1;
        dp[0][0]=1;
        for(int j=0;j<m;j++){
            for(int status=0;status<(1<<n);status++)
                for(int i=0;i<4;i++)
                    add(dp[r][trans[status][i]],dp[l][status]);
            swap(l,r);
            memset(dp[r],0,sizeof dp[r]);
        }
        int ans[20]={0};
        for(int status=0;status<(1<<n);status++)
            add(ans[__builtin_popcount(status)],dp[l][status]);
        for(int i=0;i<=n;i++)
            printf("%d\n",ans[i]);
    }
	return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值