题意:一段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;
}