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;
}

浙公网安备 33010602011771号