题目大意:
给你一个DNA序列,求有多少个长度为N的DNA序列和给定序列的LCS为0.1.2....
解题思路:
首先比较容易想到使用dp[i][s]表示:当前长度为i,给出序列中已经匹配的位置的下标状压为s,方案数。
不过我们可以发现,如果按照普通求LCS的方式进行转移的话,我们需要枚举当前添加一个字符可以转移到的所有状态再取最大值,但我们这是计数DP,这样写的话,显然会重复计算。所以我们可以考虑再套一层DP,先预处理求出任意一个状态添加一个字符可以转移到的状态,然后计数DP时直接根据这个结果向最优的方向转移即可。
AC代码:
#include <iostream> #include <algorithm> #include <cstdio> #include <cstring> #include <vector> #include <queue> using namespace std; #define mem(a,b) memset((a),(b),sizeof(a)) const int MOD=1000000007; const int MAXL=15+1; const int MAXS=1<<MAXL; const char d[]="ATCG"; char dna[MAXL]; int N, len, dp[2][MAXS], ans[MAXL]; int next_s[MAXS][4];//当前状态下,选择第j个字符,下一个状态 int pre[MAXL];//当前状态前i位有多少个1 int lcs[MAXL]; void init() { mem(dp, 0); mem(ans, 0); } inline void mod_add(int &a, const int &b) { a+=b; if(a>=MOD) a-=MOD; } void pre_work()//预处理出,每一个状态添加一个字符后,转移到的新状态 { for(int s=0;s<(1<<len);++s) { pre[0]=0; for(int i=1;i<=len;++i) pre[i]=pre[i-1]+((s>>(i-1))&1); for(int k=0;k<4;++k) { for(int i=1;i<=len;++i) { //通过在任意位置添加一个这个字符,前i位最大可以得到的LCS if(dna[i]==d[k]) lcs[i]=pre[i-1]+1; else lcs[i]=max(lcs[i-1], pre[i]); } //通过比较相邻两位的lcs,得到新状态的状压表示 int &t=next_s[s][k]=0; for(int i=1;i<=len;++i) t|=((lcs[i]!=lcs[i-1])<<(i-1)); } } } int main() { int T_T; scanf("%d", &T_T); while(T_T--) { scanf("%s%d", dna+1, &N); len=strlen(dna+1); init(); pre_work(); bool now=0, next=1; dp[now][0]=1; for(int i=0;i<N;++i) { for(int st=0;st<(1<<len);++st) for(int k=0;k<4;++k) mod_add(dp[next][next_s[st][k]], dp[now][st]); mem(dp[now], 0); now^=true; next^=true; } for(int i=0;i<(1<<len);++i) mod_add(ans[__builtin_popcount(i)], dp[now][i]); for(int i=0;i<=len;++i) printf("%d\n", ans[i]); } return 0; }