[SMOJ1810]基因测试

97 篇文章 0 订阅
8 篇文章 0 订阅

题目描述

现代的生物基因测试已经很流行了。现在要测试色盲的基因组。有 N 个色盲的人和 N 个非色盲的人参与测试。
基因组包含 M 位基因,编号 1 至 M。每一位基因都可以用一个字符来表示,这个字符是‘A’、’C’、’G’、’T’四个字符之一。
例如: N=3,M=8

色盲者1的8位基因组是: AATCCCAT
色盲者2的8位基因组是: ACTTGCAA
色盲者3的8位基因组是: GGTCGCAA
正常者1的8位基因组是: ACTCCCAG
正常者2的8位基因组是: ACTCGCAT
正常者3的8位基因组是: ACTTCCAT

通过认真观察研究,生物学家发现,有时候可能通过特定的连续几位基因,就能区分开是正常者还是色盲者。
例如上面的例子,不需要8位基因,只需要看其中连续的4位基因就可以判定是正常者还是色盲者,这4位基因编号分别是:
(第2、3、4、5)。也就是说,只需要看第2,3,4,5这四位连续的基因,就能判定该人是正常者还是色盲者。
比如:第2,3,4,5这四位基因如果是GTCG,那么该人一定是色盲者。

生物学家希望用最少的连续若干位基因就可以区别出正常者和色盲者,输出满足要求的连续基因的最少位数。

输入格式 1810.in

第一行,两个整数: N 1N500 3M500
接下来有 N 行,每一行是一个长度为 M 的字符串,第 i 行表示第 i 位色盲者的基因组。
接下来又有 N 行,每一行是一个长度为 M 的字符串,第 i 行表示第 i 位正常者的基因组。

输出格式 1810.out

一个整数。

输入样例 1810.in

3 8
AATCCCAT
ACTTGCAA
GGTCGCAA
ACTCCCAG
ACTCGCAT
ACTTCCAT

输出样例 1810.out

4


题意:给定 2n 个长度为 m 的字符串,分为两部分,要求找到一个最小长度,使得存在某个子串区间 [l,l+x) 满足一部分中的每个子串都与另一部分中的每个子串不相等,但同一部分中的可以相等。

上面这个概括可能会比较抽象,用样例来说明:
AATCCCAT
ACTTGCAA
GGTCGCAA
ACTCCCAG
ACTCGCAT
ACTTCCAT

对于所有长度为 1、2、3 的子串,无论从哪里开始,两部分中都有相等的子串。而如果子串长度为 4 呢?
从第 1 位开始的话,各个串的子串分别为 AATC、ACTT、GGTC(第一部分)、ACTC、ACTC 和 ACTT(第二部分)。可以看到,第一部分和第二部分中都出现了 ACTT,因此不能作为答案。
而如果从第 2 位开始,则可以发现各个串的子串分别为 ATCC、CTTG、GTCG(第一部分)、CTCC、CTCG 和 CTTC,在第一部分和第二部分中没有出现任何相等子串,因此答案就是 4。

首先明确一点,答案显然具有单调性。对于一个子串长度 x ,如果已经足够区分两部分,说明两部分(以某一位开头)已经没有相等子串,那么显然比 x 大的答案也是符合题意的。因此,本题可以用二分答案解决。

那么我们的问题就是,对于一个给定的子串长度 x ,如何判断它是否符合题意呢?我们知道,判断答案的可行性也正是二分答案的精髓。

既然只要从某一位开始的长度为 x 的子串能够区分两部分就可以了,那么我们不妨就枚举这个起始位,显然容易得到 2n 个长度为 x 的子串。
我们可以枚举第一部分中的每个子串,再枚举第二部分中的,一一匹配检查,但这样就有 n2 的复杂度了,再加上枚举起始位和二分答案,达到了 n3log2n 的复杂度,很明显会 TLE。

这也就意味着我们至少要降一维的复杂度,那么最好入手的就是把一个嵌套改为两个并列。
事实上,用之前“不同子串个数”一题的字符串 hash 法,可以通过预处理,很好地在 O(1) 的复杂度内算出一个子串的 hash 值,于是便可以在判断某个长度是否可行的时候,枚举起始位,先将第一部分的子串 hash 值标记,再计算第二部分的子串 hash 值,看是否被标记过,即可。

这样一来,就可以在 O(n2log2n) 的时间内完美地解决这题了。

参考代码:

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

using namespace std;

typedef unsigned long long ull;

const int maxn = 1010;
const int maxm = 510;
const int prime = 30000007;

int n, m;
int str[maxn][maxm];

ull p[maxn];
ull sum[maxn][maxm];

bool hash[prime];

bool check(int x) {
    for (int i = 1; i + x != m + 2; ++i) { //起始位
        bool bo = true;
        for (int j = 0; j != n; ++j) hash[(sum[j][i + x - 1] - sum[j][i - 1] * p[x] + prime) % prime] = true; //标记色盲
        for (int j = n; j != (n << 1); ++j)
            if (hash[(sum[j][i + x - 1] - sum[j][i - 1] * p[x] + prime) % prime]) { bo = false; break; } //检查非色盲
        for (int j = 0; j != n; ++j) hash[(sum[j][i + x - 1] - sum[j][i - 1] * p[x] + prime) % prime] = false; //清空
        if (bo) return true;
    }
    return false;
}

int main(void) {
    freopen("1810.in", "r", stdin);
    freopen("1810.out", "w", stdout);
    scanf("%d%d", &n, &m); getchar();
    p[0] = 1;
    for (int i = 1; i != m + 1; ++i) p[i] = (p[i - 1] << 2) % prime; //权的预处理
    for (int i = 0; i != (n << 1); ++i) {
         for (int j = 1; j != m + 1; ++j) {
            char c;
            c = getchar(); //常数优化。。。
            if (c == 'A') str[i][j] = 0; //重编码为 4 进制方便 hash
            else if (c == 'C') str[i][j] = 1;
            else if (c == 'G') str[i][j] = 2;
            else str[i][j] = 3;
            sum[i][j] = (((sum[i][j - 1] << 2) % prime) + str[i][j]) % prime;
         }
         getchar();
    }
    int l = 1, r = m;
    while (l + 1 != r) {
        int mid = l + r >> 1;
        if (check(mid)) r = mid; else l = mid;
    }
    if (check(l)) printf("%d\n", l); else printf("%d\n", r);
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值