【BZOJ 4892】DNA

又一个DNA

【题意】

加里敦大学的生物研究所,发现了决定人喜不喜欢吃藕的基因序列S,有这个序列的碱基序列就会表现出喜欢吃藕的性状,但是研究人员发现对碱基序列S,任意修改其中不超过3个碱基,依然能够表现出吃藕的性状。现在研究人员想知道这个基因在DNA链S0上的位置。所以你需要统计在一个表现出吃藕性状的人的DNA序列S0上,有多少个连续子 串可能是该基因,即有多少个S0的连续子串修改小于等于三个字母能够变成S。
字符串长度 ≤ 1 0 5 \leq 10^5 105

【分析】

发现我们需要关注的不同位置最多只有三个。也就是说S在S0中最多被分为4段。那么我们只需要判断:对于所有连续的子串,给它们三次机会,能否成功与S匹配。
不难把这个问题转化为求三次最长公共前缀(LCP)。其实可以用后缀数组处理,但是我不会。
换个思路。发现由于公共前缀的长度满足单调性,于是想到用二分查找,然后判断字符串是否相等即可。我们可以用字符串哈希解决这个问题。
预处理+枚举子串 O ( n ) O(n) O(n),二分 O ( l o g n ) O(logn) O(logn),判断相等 O ( 1 ) O(1) O(1),于是时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

【代码】
/*其实可以模大质数而非自然溢出来降低冲突率,然而常数太大过不了*/
#include<bits/stdc++.h>
#define ll unsigned long long
using namespace std;
const int mn = 100005, v1 = 11, v2 = 13;
char a[mn], b[mn], v[] = {'A', 'C', 'G', 'T'};
ll s1[mn], t1[mn], q1[mn];
ll s2[mn], t2[mn], q2[mn];
int n, m;
inline ll query(int l, int r, ll* s, ll* p) {return s[r] - s[l - 1] * p[r - l + 1];}
inline int getlcp(int p1, int p2)
{
    int l = 1, r = m, ret = 0;
    while(l <= r)
    {
        int mid = (l + r) >> 1;
        if(query(p1, p1 + mid - 1, s1, q1) == query(p2, p2 + mid - 1, t1, q1) && query(p1, p1 + mid - 1, s2, q2) == query(p2, p2 + mid - 1, t2, q2))
            ret = mid, l = mid + 1;
        else
            r = mid - 1;
    }
    return ret;
}
inline bool check(int l)
{
    int r = l + m - 1, now = 1;
    for(int i = 0; i < 3; i++)
    {
        int len = getlcp(l, now);
        now += len + 1; l += len + 1;
        if(now > m) return 1;
    }
    return query(l, r, s1, q1) == query(now, m, t1, q1) && query(l, r, s2, q2) == query(now, m, t2, q2);
}
int main()
{
    int T;
    scanf("%d", &T), q1[0] = q2[0] = 1;
    for(int i = 1; i <= mn - 5; i++)
        q1[i] = q1[i - 1] * v1, q2[i] = q2[i - 1] * v2;
    while(T--)
    {
        scanf("%s%s", a + 1, b + 1);
        n = strlen(a + 1), m = strlen(b + 1);
        for(int i = 1; i <= n; i++)
            for(int j = 0; j < 4; j++)
                if(a[i] == v[j])
                    a[i] = j;
        for(int i = 1; i <= m; i++)
            for(int j = 0; j < 4; j++)
                if(b[i] == v[j])
                    b[i] = j;
        s1[1] = s2[1] = a[1], t1[1] = t2[1] = b[1];
        for(int i = 2; i <= n; i++)
            s1[i] = s1[i - 1] * v1 + a[i], s2[i] = s2[i - 1] * v2 + a[i];
        for(int i = 2; i <= m; i++)
            t1[i] = t1[i - 1] * v1 + b[i], t2[i] = t2[i - 1] * v2 + b[i];
        int ans = 0;
        for(int i = 1; i + m - 1 <= n; i++)
            ans += check(i);
        printf("%d\n", ans);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值