Java B组蓝桥杯第七届国赛:碱基

38 篇文章 1 订阅
32 篇文章 2 订阅

碱基

生物学家正在对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,否则按无效代码处理。

这道题,看题目半个小时愣是不知道在干啥,还有干扰语句

准确的说,科学家关心的序列用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]。

这......做整个题丝毫没用到。我简单把题目转换一下:

输入三个数,n,m,k,

然后输入n行任意长度字符串,(字符串只由A,T,C,G四个字符组成,这句话也没什么用)

任意取出m行字符串,它们含有相同k个长度的连续字符串的情况称作为1个元组,位置不同则为不同的元组

例如 输入:

        3 2 2
        ATC
        ATATG
        ACG

任取两行,找它们含有2个长度的连续字符串的情况

比如取前两行,它们含有2个元组,分别是AT,和 AT,因为第二行字符串中含有两个AT,分别与第一个AT组成不同的元组。

如果将第一行字符串换成ATATC,那它们将含有4个元组,因为排列组合,或者直接每行个数相乘,得到元组个数

目标:要统计n行字符串中含有多少个元组。

 

代码:参考了一位大佬的代码,在其基础上改良了一下,我的注释写的应该很详细

import java.util.Scanner;

public class Main {
	int n, m, k;// 一共n个生物,至少有m个生物出现长度为k的连续碱基序列
	int sum;
	String[] DNA;// n种生物DNA,字符串存储便于切割
	String[] temp;// 临时存放m个生物的DNA

	public Main() {
		Scanner sn = new Scanner(System.in);
		n = sn.nextInt();
		m = sn.nextInt();
		k = sn.nextInt();
		DNA = new String[n];
		for (int i = 0; i < n; i++) {
			DNA[i] = sn.next();
		}
		temp = new String[m];
		//进入排序统计
		dfs(0, 0);
		System.out.println(sum);
	}

	/*
	 * 任选m个生物的DNA:
	 *  对于n个生物DNA,全排列取前m位 因为不要求顺序,
	 *  因此全排列中会存在 顺序不同但内容重复 的序列
	 *  想要去重很简单,按顺序,依次抽取m个DNA 
	 *  这样好处比如不存在抽取DNA[5]之后又能抽取DNA[4]的情况
	 *  
	 *  
	 *  dfs( position表示抽取DNA的位置     ,  x表示已经抽取了几个DNA  )
	 */
	public void dfs(int position, int x) {
		if (x == m) {
			sum += getCount();
			sum %= 1000000007;
			return;
		}
		for (int i = position; i < n; i++) {
			temp[x] = DNA[i];
			//这里传入i+1,避免抽取DNA[i]之后又能抽取DNA[1~i-1]而造成的重复
			dfs(i + 1, x + 1);
		}

	}

	//统计抽取m个DNA后专家需要关注的元组个数,一个元组说白了就是m个DNA都含有的k个连续碱基序列的一种情况
	public int getCount() {
		int re = 0;//统计本次结果的参数
		//这里实际上是采用了一张hash二维表
		int table[][] = new int[m][100000];
		//将每个DNA进行处理
		for (int i = 0; i < m; i++) {
			//将DNA按k长度,依次截取,得到的连续碱基序列通过哈希编码存储到hash表中相对应的位置
			for (int j = 0; j < temp[i].length() - k + 1; j++) {
				String str = temp[i].substring(j, j + k);
				//如果重复会累加重复次数,因为题目中说两个连续碱基序列对比如果在DNA的位置不同,则为两种不同的情况,重复次数后面需要用到
				table[i][str.hashCode() % 100000]++;
			}
		}
		//遍历hash表,统计次数,根据题目意思转换,这里将第一个DNA产生的多个位置作为参照,
		for (int i = 0; i < 100000; i++) {
			int count = 1;
			if (table[0][i] != 0) {
				//统计他们的乘积,如果有一项为0,则表示当前列不是被关注的元组
				//则会导致整个连续碱基序列不是科学家所关注的元组,同时其乘积也为0,不会对最终统计结果产生影响
				for (int j = 0; j < m; j++) {
					count *= table[j][i];
				}
				//统计元组情况个数
				re += count;
			}
		}

		return re;

	}

	public static void main(String[] args) {
		new Main();
	}

}

这里讲一下重点部分,利用哈希二维表存放连续字符串序列,也就是存放连续碱基序列。

int table[][] = new int[m][100000];

m是m行,100000是任意取值的,但根据哈希存储的特性,不同的字符串对应不同的哈希编码,String.hashCode()可以直接得到。对100000求余后存储在数组对应位置。这个100000,可以更改,但取值决定这字符串存储是两个不同的字符串可能会存储在一个位置,因为他们求余之后的结果可能会重复。因此根据内容而定,越大越好,但太大也会浪费空间。具体为什么,学过数据结构都懂,不懂可以百度学习,这里就不多说了。

重点是这里用hash表好处就是方便统计元组个数,用我上面的那个ATC和ATATG举例,m=2,k=2。对应到下表

 ATTCTATG
ATC1100
ATATG201

1

统计元组的方法,每一列乘积之后的和就是元组的个数。

原因:首先一列中如果含有0,那这一列不是元组,并且乘积为0,求和之后对最终结果不会产生影响。如果都大于0,则乘积的方式相当于是排列组合,这个情况在上面举得例子中详细讲过。

这是一种取m行的情况,还需要对n行进行全排列,去重之后,统计每种情况的元组个数之和,就是最终答案。

 

 

 

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值