题意:只含字母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;
}