• @bzoj



    @description - translation@

    给定一个基因串 S(仅由 A,G,C,T 组成的串)。给出另一个基因串 T 的长度 m。
    对于每一个 0 <= i <= |S|,求出所有 4^m 种可能的基因串 T 有多少满足 LCS(S, T) = i。
    LCS:最长公共子序列。

    input
    多组数据。第一行输入数据组数 T,T <= 5。
    接下来每组数据,第一行一个基因串 S,|S| <= 15。第二行一个整数 m, m <= 1000。

    output
    对于每组数据,每一行输出当 i = 0, 1,...,|S| 相应的答案,膜 10^9 + 7。

    sample input
    1
    GTC
    10
    sample output
    1
    22783
    528340
    497452

    @solution@

    好像学名叫作 dp 套 dp。

    Candy? dalao 给出了这种类型的题目的一般化描述:

    通过一个外层的 dp 来计算使得另一个 dp 方程 (子dp) 最终结果为特定值的输入数。

    甚至还给出这种这种问题的一般化解法:

    一位一位确定子 dp 的输入,记录子 dp 的状态值。

    我们考虑求两个串 S,T 正常 LCS 的求解过程:

    [dp(i,j)=egin{cases} 0&i==0或j==0\ max{dp(i, j-1), dp(i-1, j)}&S[i] ot =T[j]\ dp(i-1, j-1) + 1 & S[i] = T[j] end{cases}]

    S 串是已知的。因此我们大致的思路是,从前往后一位一位地确定 T 串的值,同时维护 dp 数组不同取值对应的方案数。

    听起来好像很抽象。
    (f[s][j]) 表示 ({dp[0][j], dp[1][j], dots , dp[n][j]}) 的状态为 (s) 的方案数。
    我们通过枚举 T 串第 j 位的字符,算出 ({dp[0][j+1], dp[1][j+1], dots , dp[n][j+1]}) 对应的状态 (s'),再由 (f[s][j]) 转移到 (f[s'][j+1])

    听起来好像还是很抽象。不管了我们看一个更重要的问题。
    (dp[i][j]) 的值可能很波动,怎么把这个状态存下来呢?
    这个时候就涉及到 (LCS) 这玩意儿本身的性质:

    [dp[i][j] = dp[i-1][j] 或 dp[i-1][j]+1 ]

    于是我们把 dp 数组差分一下,得到的差分数组就是 01 数组了,就可以正常状压了。
    怎么说明上面那个性质呢?可以从 (LCS) 的定义感性认知一下。

    如果真的暴力做的话,时间复杂度是 (O(T*2^{|S|}*|S|*m*4)),是肯定 TLE 的。
    怎么办呢?我们转移 (f) 的时候,先把所有的情况 (O(4*|S|*2^{|S|})) 预处理了,转移的时候复杂度就是 (O(2^{|S|}*m*4)) 的啦。

    @accepted code@

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int MAXS = 15;
    const int MAXM = 1000;
    const int MOD = int(1E9) + 7;
    const char DNA[] = {'A', 'C', 'G', 'T'};
    int bits[1<<MAXS], trans[1<<MAXS][4];
    //原来 trans 是 transfer 的简写,而不是 translate 啊qwq
    //我英语真的很差 qwq 
    char S[MAXS + 5]; int m;
    int f[2][1<<MAXS], ans[MAXS + 5];
    int a[MAXS + 5], b[MAXS + 5];
    void solve() {
    	scanf("%s%d", S, &m);
    	int len = strlen(S), t = (1<<len);
    	for(int s=0;s<t;s++) { 
    		f[0][s] = 0;
    		for(int k=0;k<4;k++) {
    			for(int l=0;l<len;l++)
    				a[l+1] = a[l] + ((s & (1<<l)) ? 1 : 0); 
    			for(int l=0;l<len;l++)
    				b[l+1] = (DNA[k] == S[l]) ? a[l] + 1 : max(a[l+1], b[l]);
    			int s0 = 0;
    			for(int l=len;l>=1;l--)
    				s0 = (s0 << 1) | (b[l] - b[l-1]);
    			trans[s][k] = s0;
    		}
    	} 
    	f[0][0] = 1;
    	for(int i=1;i<=m;i++) {
    		for(int s=0;s<t;s++)
    			f[i&1][s] = 0;
    		for(int s=0;s<t;s++)
    			for(int k=0;k<4;k++)
    				f[i&1][trans[s][k]] = (f[i&1][trans[s][k]] + f[i&1^1][s])%MOD;
    	}
    	for(int i=0;i<=len;i++)
    		ans[i] = 0;
    	for(int s=0;s<t;s++)
    		ans[bits[s]] = (ans[bits[s]] + f[m&1][s])%MOD;
    	for(int i=0;i<=len;i++)
    		printf("%d
    ", ans[i]);
    }
    void init() {
    	for(int i=1;i<=(1<<MAXS);i++)
    		bits[i] = bits[i>>1] + (i&1);
    }
    int main() {
    	init();
    	int T; scanf("%d", &T);
    	for(int i=1;i<=T;i++)
    		solve();
    }
    

    @details@

    该死的运算符优先级,C++ 可真是神奇 QAQ。
    yhn 学长好像不是用 dp 套 dp 的思想来想的,不过也 AC 了这道题?
    不管了不管了 qwq。

  • 相关阅读:
    面向对象的核心概念
    堆栈和托管堆 c#
    DIV绘制图形
    slideLeft DIV向左右滑动
    结构化分析
    函数
    HTML CSS 特殊字符表
    100以内的数是正整数 if的基本用法
    简单三个数比较大小 “?!”的用法
    简单计算器
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/10183970.html
Copyright © 2020-2023  润新知