题目链接:
(1)http://www.lydsy.com/JudgeOnline/problem.php?id=3864
(2)http://acm.hdu.edu.cn/showproblem.php?pid=4899
题目大意:
给你一个只由AGCT组成的字符串
S(|S|≤15)
,对于每个
0≤i≤|S|
,问
有多少个只由AGCT组成的长度为
m(1≤m≤1000)
的字符串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
来表示这个过程,
可以用DP套DP来求解。枚举LCS的DP值转移状态
然后求
f[][]
数组,
f[len][S]=
长度为
len
,dp值转移状态为
S
的不同的
最终与
S
串LCS长度为
代码:
#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;
}