【BZOJ 4892】DNA

又一個DNA

【題意】

加里敦大學的生物研究所,發現了決定人喜不喜歡喫藕的基因序列S,有這個序列的鹼基序列就會表現出喜歡喫藕的性狀,但是研究人員發現對鹼基序列S,任意修改其中不超過3個鹼基,依然能夠表現出喫藕的性狀。現在研究人員想知道這個基因在DNA鏈S0上的位置。所以你需要統計在一個表現出喫藕性狀的人的DNA序列S0上,有多少個連續子 串可能是該基因,即有多少個S0的連續子串修改小於等於三個字母能夠變成S。
字符串長度105\leq 10^5

【分析】

發現我們需要關注的不同位置最多隻有三個。也就是說S在S0中最多被分爲4段。那麼我們只需要判斷:對於所有連續的子串,給它們三次機會,能否成功與S匹配。
不難把這個問題轉化爲求三次最長公共前綴(LCP)。其實可以用後綴數組處理,但是我不會。
換個思路。發現由於公共前綴的長度滿足單調性,於是想到用二分查找,然後判斷字符串是否相等即可。我們可以用字符串哈希解決這個問題。
預處理+枚舉子串O(n)O(n),二分O(logn)O(logn),判斷相等O(1)O(1),於是時間複雜度爲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);
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章