【题解】3747. Problem C

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,jfi,j1[0,1] ,所以 S S S 就可以表示 f i , j − f i , j − 1 f_{i,j}-f_{i,j-1} fi,jfi,j1 的状态,最大有 2 10 2^{10} 210 个。

则容易得出转移 F i , S → F i + 1 , T F_{i,S}\rightarrow F_{i+1,T} Fi,SFi+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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值