3747. Problem C
题目大意
给定一个字符串 s s s 由 A , C , G , T A,C,G,T A,C,G,T 组成,求长度为 m m m 且同样由 A , C , G , T A,C,G,T A,C,G,T 组成的字符串中,与 S S S 的最长公共子序列长度 ∈ [ 0 , n ] \in[0,n] ∈[0,n] 的分别有几个。
思路
经典dp套dp。
第一次接触。
首先回顾求最长公共子序列的方法:设 f i , j f_{i,j} fi,j 表示做到长度为 m m m 的字符串第 i i i 位, s s s 的第 j j j 位,然后转移。
本题我们设 F i , S F_{i,S} Fi,S 表示做到长度为 m m m 的字符串第 i i i 位, f i , j f_{i,j} fi,j 的状态为 S S S 的方案数。但我们会发现,方案数有 1 0 10 10^{10} 1010 ,无法接受。但我们发现 f i , j − f i , j − 1 ∈ [ 0 , 1 ] f_{i,j}-f_{i,j-1}\in[0,1] fi,j−fi,j−1∈[0,1] ,所以 S S S 就可以表示 f i , j − f i , j − 1 f_{i,j}-f_{i,j-1} fi,j−fi,j−1 的状态,最大有 2 10 2^{10} 210 个。
则容易得出转移 F i , S → F i + 1 , T F_{i,S}\rightarrow F_{i+1,T} Fi,S→Fi+1,T ,考虑如何求出 T T T ,首先,我们可以通过 S S S 求出 f i , j f_{i,j} fi,j。其次枚举这一位放 A , C , G , T A,C,G,T A,C,G,T 的哪一个,通过求最长公共子序列的方法求出 f i + 1 , j f_{i+1,j} fi+1,j ,再通过 f i + 1 , j f_{i+1,j} fi+1,j 求出 T T T ,可预处理。
答案统计: a n s f m , n + = F m , S ans_{f_{m,n}}+=F_{m,S} ansfm,n+=Fm,S ,其中 f m , n f_{m,n} fm,n 由 F m , S F_{m,S} Fm,S 推出。容易发现 f m , n = p o p c o u n t ( S ) f_{m,n}=popcount(S) fm,n=popcount(S) 。
Code
#include <cstdio>
#include <iostream>
using namespace std;
const int N = 1100;
const int P = 1e9 + 7;
int n, m;
int t[N][5], F[N][N], ans[N], s[N], f[N], g[N];
int p(char ch) {
if (ch == 'A') return 1;
if (ch == 'C') return 2;
if (ch == 'G') return 3;
return 4;
}
int count(int x) {
int res = 0;
for (; x; x -= x & -x) ++res;
return res;
}
int main() {
char ch = getchar();
for (; isupper(ch); ch = getchar()) s[++n] = p(ch);
scanf("%d", &m);
for (int S = 0; S < (1 << n); ++S) {
f[0] = g[0] = 0;
for (int i = 1; i <= n; ++i)
f[i] = f[i - 1] + ((S >> (i - 1)) & 1);
for (int c = 1; c <= 4; ++c) {
int T = 0;
for (int i = 1; i <= n; ++i) {
g[i] = max(g[i - 1], f[i]);
if (s[i] == c) g[i] = max(g[i], f[i - 1] + 1);
}
for (int i = 1; i <= n; ++i)
T |= (1 << (i - 1)) * (g[i] - g[i - 1]);
t[S][c] = T;
}
}
F[0][0] = 1;
for (int i = 0; i < m; ++i) {
for (int S = 0; S < (1 << n); ++S) {
for (int j = 1; j <= 4; ++j)
(F[i + 1][t[S][j]] += F[i][S]) %= P;
}
}
for (int S = 0; S < (1 << n); ++S) (ans[count(S)] += F[m][S]) %= P;
for (int i = 0; i <= n; ++i) printf("%d\n", ans[i]);
return 0;
}