P3763 [TJOI2017] DNA

P3763 [TJOI2017] DNA

题目描述

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

输入格式a

第一行有一个整数 \(T\),表示有几组数据。

每组数据第一行一个长度不超过 \(10^5\) 的碱基序列 \(S_0\)

每组数据第二行一个长度不超过 \(10^5\) 的吃藕基因序列 \(S\)

输出格式

\(T\) 行,第 \(i\) 行表示第 \(i\) 组数据中,在 \(S_0\)中有多少个与 \(S\) 等长的连续子串可能是表现吃藕性状的碱基序列。

说明/提示

对于 \(20\%\) 的数据,\(S_0,S\) 的长度不超过 \(10^4\)

对于 \(100\%\) 的数据,\(S_0,S\) 的长度不超过 \(10^5\)\(0\lt T\leq 10\)

注:DNA 碱基序列只有 ATCG 四种字符。

Solution:

本来以为字符集大小 \(\le4\) 是突破口,然后发现字符集大小根本不重要。

首先我们思考一下如何匹配两个字符串,\(kmp\) 固然可以,但是本题中允许三个位置不相同,这样我们的 \(kmp\) 就不太好写了。所以我们考虑使用 hash。

当我们将两个字符串的 hash 求出来之后,\(check\) 他们是否相等的时间是 \(O(1)\) 的,所以我们就可以枚举 \(S\)\(S_0\) 上出现的位置 \(st_s0\) 来匹配,那么怎么刻画“允许至多三个位置不相同”呢:

我们发现,匹配长度是满足单调性的,所以我们二分一个最大匹配长度 \(len\) 代表 \([st_{s0},st_ {s0}+len]\)\([st_s,st_s+len]\) 是匹配的,且 \(s0[st_{s0}+len+1] \ne s[st_s+len+1]\) 然后我们跳过 $s0[st_{s0}+len+1]\ ,\ s[st_s+len+1] $ 这两个点,重新以 \(st_{s0}+len+2 \ ,\ st_s+len+2\) 为点匹配,这样的失配最多出现 3 次,否则返回 false

然后这题就做完了,时间复杂度 \(O(nlogn)\)

Code:

#include<bits/stdc++.h>
#define ull unsigned long long
const int N=1e5+5;
const ull P=19991;
using namespace std;
ull h[2][N],qpow[N];
void init(){qpow[0]=1;for(int i=1;i<N;i++)qpow[i]=qpow[i-1]*P;}
ull Hash(ull h[],int l,int r)
{
    return h[r]-h[l-1]*qpow[r-l+1];
}
int match_len(int st_s0,int st_s,int ed_s)
{
    int l=1,r=ed_s-st_s+2,res=0;
    while(l<=r)
    {
        int mid=l+r>>1;
        if(Hash(h[0],st_s0,st_s0+mid-1)==Hash(h[1],st_s,st_s+mid-1))res=mid,l=mid+1;else r=mid-1;
    }
    return res;
}
bool check(int st_s0,int ed_s0,int st_s,int ed_s)
{
    int len=ed_s-st_s;
    for(int i=1;i<=3;i++)
    {
        int k=match_len(st_s0,st_s,ed_s);
        st_s0+=k+1,st_s+=k+1;
        if(st_s>ed_s)return 1;
    }
    return Hash(h[0],st_s0,ed_s0)==Hash(h[1],st_s,ed_s);
}
char s[2][N];
int n,m,ans;
void work()
{
    ans=0;
    scanf("%s",s[0]+1);n=strlen(s[0]+1);
    scanf("%s",s[1]+1);m=strlen(s[1]+1);
    for(int i=1;i<=n;i++)h[0][i]=h[0][i-1]*P+s[0][i];
    for(int i=1;i<=m;i++)h[1][i]=h[1][i-1]*P+s[1][i];
    for(int i=1;i+m-1<=n;i++)if(check(i,i+m-1,1,m))ans++;
    printf("%d\n",ans);
}
int main()
{
    //freopen("P3763.in","r",stdin);
    //freopen("P3763.out","w",stdout);
    init();int T;cin>>T;
    while(T--)work();
    return 0;
}
posted @ 2025-02-26 17:08  liuboom  阅读(24)  评论(0)    收藏  举报