蓝桥杯2016年第七届真题-碱基

题目

题目链接


生物学家正在对n个物种进行研究。
其中第i个物种的DNA序列为s[i],其中的第j个碱基为s[i][j],碱基一定是A、T、G、C之一。
生物学家想找到这些生物中一部分生物的一些共性,他们现在关注那些至少在m个生物中出现的长度为k的连续碱基序列。准确的说,科学家关心的序列用2m元组(i1,p1,i2,p2…im,pm)表示,
满足:
1<=i1<i2<…<im<=n;
且对于所有q(0<=q<k), s[i1][p1+q]=s[i2][p2+q]=…=s[im][pm+q]。

现在给定所有生物的DNA序列,请告诉科学家有多少的2m元组是需要关注的。如果两个2m元组有任何一个位置不同,则认为是不同的元组。

【输入格式】
输入的第一行包含三个整数n、m、k,两个整数之间用一个空格分隔,意义如题目所述。
接下来n行,每行一个字符串表示一种生物的DNA序列。
DNA序列从1至n编号,每个序列中的碱基从1开始依次编号,不同的生物的DNA序列长度可能不同。

【输出格式】
输出一个整数,表示关注的元组个数。
答案可能很大,你需要输出答案除以1000000007的余数。

【样例输入】
3 2 2
ATC
TCG
ACG

【样例输出】
2

再例如:
【样例输入】
4 3 3
AAA
AAAA
AAA
AAA

【样例输出】
7

【数据规模与约定】
对于20%的数据,k<=5,所有字符串总长L满足L <=100
对于30%的数据,L<=10000
对于60%的数据,L<=30000
对于100%的数据,n<=5,m<=5,1<=k<=L<=100000
保证所有DNA序列不为空且只会包含’A’ ’G’ ’C’ ’T’四种字母

资源约定:
峰值内存消耗 < 256M
CPU消耗 < 1000ms

请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。

所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
注意:不要使用package语句。不要使用jdk1.7及以上版本的特性。
注意:主类的名字必须是:Main,否则按无效代码处理。

题解

暴力+dfs。


首先由于网站上并未给出数据规模,因此我这里特意找了一下规模,发现n<=5,m<=5,这就好说了。

本题的本质思路就是暴力,只不过在其中的一些实现上不得不使用dfs,也就是说dfs只是工具。


整体思路:
我们得存俩东西,一个是每个字符串的每种长度为k的子串的个数(用 A A A代指),另一个要存全部字符串不同的长度为 k k k的子串有哪些(用 B B B代指);
我们遍历 B B B,对于枚举到的每个子串 s u b s t r substr substr,我们都遍历 A A A n n n个字符串中 s u b s t r substr substr的个数,若其 s u b s t r substr substr个数大于 0 0 0,则将 s u b s t r substr substr的个数加入到一个数组(vector)中;若数组中的元素个数大于等于 m m m则说明至少可以构成一个我们需要的序列;
假设数组中的元素有 t t t个,我们从中选 m m m个的方案数就是组合数 C t m C_t^m Ctm吧,这只是组合数,我们实际要将选出来的这 m m m个数进行相乘,最后再把每一种组合的乘积相加,就得到了对于子串 s u b s t r substr substr能得到的满足要求的序列数了,最终我们将全部子串的序列数相加即为答案,乘加过程中取模即可;
t t t个数中选 m m m个的乘积需要通过dfs求,求法也很简单,对于某个位置的数无非就是选和不选两种情况。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 10;
const ll MOD = 1000000007;

int n, m, k;
ll ans, res;
string s;
vector<int> v; // 代码中讲 
map<string, int> cnt[N], vis; // cnt[i][str]表示第i个字符串中长度为k的子串str的个数   vis[str]表示str是否为全部字符串中某个字符串的长度为k的子串 

void dfs(int x, int num, ll sum) { // x表示v中的索引,num表示已选个数,sum表示已选数的乘积 
	
	if(num == m) {res = (res + sum)%MOD; return ;}
	if(x == v.size()) return ;
	
	dfs(x+1, num, sum);
	dfs(x+1, num+1, sum*v[x]%MOD);
}

int main()
{
	cin>>n>>m>>k;
	for(int i = 1;i <= n;i ++) {
		cin>>s;
		int nn = s.size();
		for(int j = 0;j < nn-k+1;j ++) {
			string str = s.substr(j, k); // 获取全部长度为k的子串 
			cnt[i][str] ++; // 第i个字符串的子串str个数+1 
			vis[str] = 1; // 标记存在子串str 
		}
	}	
	 
	for(map<string,int>::iterator it = vis.begin();it != vis.end();it ++) { // 枚举子串 
		v.clear(); // !!!
		for(int i = 1;i <= n;i ++) {
			string tmp = it->first;
			int num = cnt[i][tmp]; // 第i个字符串中子串tmp的个数 
			if(num) v.push_back(num); // 第i个字符串中子串tmp的个数不为0,则加入数组中 
		}
		if(v.size() >= m) {
			res = 0LL; // res用于暂存每种组合的乘积 
			dfs(0, 0, 1LL);
			ans = (ans + res) % MOD;
		}
	}
	cout << ans;

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不牌不改

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值