Luogu P4591 [TJOI2018]碱基序列

发布时间 2023-06-10 19:14:16作者: 觉清风

[TJOI2018]碱基序列

题目描述

小豆参加了生物实验室。在实验室里,他主要研究蛋白质。他现在研究的蛋白质是由\(k\)个氨基酸按一定顺序构成的。每一个氨基酸都可能有\(a\)种碱基序列\(s_{i,j}\)构成。

现在小豆有一个碱基串\(s\),小豆想知道在这个碱基上都多少中不同的组合方式可能得到这个蛋白质。即求由\(k\)段字符串有序合并成的字符串\(s_1\),有多少种不同方式能够匹配字符串\(s\),其中\(k\)段字符串的选法不同,或者与\(s\)匹配上的位置不同认为是不同的方式。

输入格式

第一行一个数,表示这个蛋白质由\(k\)个氨基酸。

第二行一个字符串\(s\),表示小豆现在有的碱基串。

第三行开始接下来\(k\)行表示第\(i\)个氨基酸可能的碱基序列,对于第\(i\)个氨基酸,\(a_i\)表示这个氨基酸可能的碱基序列种数,接下来\(a_i\)个字符串表示这\(a_i\)种可能的碱基序列,用空格隔开。

输出格式

输出一个数目标是不同的方案数(不同的方案数是指不同的子碱基串或者相同的碱基串不同的氨基酸排列方式)

答案对\(10^9+7\)取模

样例 #1

样例输入 #1

2
ABC
2 A AB
2 C BC

样例输出 #1

2

样例 #2

样例输入 #2

2
AAA
2 A AA
2 A AA

样例输出 #2

4

提示

样例解释1

第一个选\(A\)第二个选\(C\),得到\(AC\)能够与\(ABC\)产生\(0\)中匹配方式

第一个选\(A\)第二个选\(BC\),得到\(ABC\)能够与\(ABC\)产生\(1\)中匹配方式

第一个选\(AB\)第二个选\(C\),得到\(ABC\)能够与\(ABC\)产生\(1\)中匹配方式

第一个选\(AB\)第二个选\(BC\),得到\(ABBC\)能够与\(ABC\)产生\(0\)中匹配方式

所以一共2种

样例解释2

第一个选\(A\)第二个选\(A\),得到\(AA\)能够与\(AAA\)产生\(2\)中匹配方式

第一个选\(A\)第二个选\(AA\),得到\(AAA\)能够与\(AAA\)产生\(1\)中匹配方式

第一个选\(AA\)第二个选\(A\),得到\(AAA\)能够与\(AAA\)产生\(1\)中匹配方式

第一个选\(AA\)第二个选\(AA\),得到\(AAAA\)能够与\(AAA\)产生\(0\)中匹配方式

所以一共4种

数据范围

对于\(30\%\)的数据,\(1\leq k\leq25,|s|\leq10000,a_i\leq3\)

对于\(100\%\)的数据,\(1\leq k\leq100,|s|\leq10000,a_i\leq10\)

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>

using namespace std;

typedef unsigned long long ull;
int k, ans, mod = 1000000007;
int cnt[5211314];
ull base[5211314], num[5211314], pre[521][1314];
string seq[521][13], amino;
int dp[521][131400];
//dp[i][j]表示到第i层碱基序列 蛋白质在j位置上对应的最长连接序列

int main() {
	scanf("%d", &k);
	cin >> amino;
	base[0] = 1;
	for (int i = 1; i <= amino.size(); ++ i) {
		base[i] = base[i - 1] * 131;
		//提前预处理出来base
	}
	for (int i = 1, temp; i <= k; ++ i) {
		scanf("%d", &temp);
		cnt[i] = temp;
		for (int j = 1; j <= cnt[i]; ++ j) {
			cin >> seq[i][j];
			for (int q = 0; q < seq[i][j].size(); ++ q) {
				pre[i][j] = pre[i][j] * base[1] + (ull)seq[i][j][q];
				//进行哈希预处理
			}
		}
	}
	num[0] = (ull)amino[0];
	for (int i = 1; i < amino.size(); ++ i) {
		num[i] = num[i - 1] * base[1] + (ull)amino[i];
	}
	for (int i = 0; i <= amino.size(); ++ i) dp[0][i] = 1;
	for (int i = 1; i <= k; ++ i) {
		//在第i层
		for (int j = 1, len; j <= cnt[i]; ++ j) {
			//第j个碱基序列
			len = seq[i][j].size();
			for (int q = len - 1, l, r; q < amino.size(); ++ q) {
				//将当前碱基序列与蛋白质的每一段相同长度的序列进行比较
				ull temp;
				l = q - len + 1, r = q;
				if (l == 0) temp = num[q];
				else {
					temp = num[r] - num[l - 1] * base[r - l + 1];
				}
				if (temp == pre[i][j]) {
					//若相同则从上一状态转移
					dp[i][q + 1] += dp[i - 1][q - len + 1];
					dp[i][q + 1] %= mod;
				}
			}
		}
	}
	for (int i = 0; i < amino.size(); ++ i) {
		ans += dp[k][i + 1];
		//找对应的值的和
		ans %= mod;
	}
	printf("%d\n", ans);
	return 0;
}