Description
加里敦大学的生物研究所,发现了决定人喜不喜欢吃藕的基因序列S,
有这个序列的碱基序列就会表现出喜欢吃藕的性状。
但是研究人员发现对碱基序列S,任意修改其中不超过3个碱基,依然能够表现出吃藕的性状。
现在研究人员想知道这个基因在DNA链S0上的位置。
所以你需要统计在一个表现出吃藕性状的人的DNA序列S0上,有多少个连续子串可能是该基因。
即有多少个S0的连续子串修改小于等于三个字母能够变成S。
Input
第一行有一个数T,表示有几组数据
每组数据第一行一个长度不超过10^5的碱基序列S0
每组数据第二行一个长度不超过10^5的吃藕基因序列S
Output
共T行,第i行表示第i组数据中,在S0中有多少个与S等长的连续子串可能是表现吃藕性状的碱基序列
Sample Input
1
ATCGCCCTA
CTTCA
ATCGCCCTA
CTTCA
Sample Output
2
题解Here!
这题解法比较多。
后缀数组:
先求出后缀数组,然后对$height$数组建立$RMQ$。
于是$LCP$就可以$O(1)$求了。
我们枚举原串的每一个字符作为起点,如果相同就用$lcp$求,不相同就给记录不相同的计数器$++$。
保证不相同的计数器不超过$3$。
如果在$3$以内我们就统计答案。
注:这个好像在BZOJ上被卡常了,我也不知道为什么。。。
据说$SAM$跑得飞快,啥时候去学一学。。。
附代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#define MAXN 200010
using namespace std;
int n,m;
int top,sa[MAXN],rk[MAXN],height[MAXN],tax[MAXN],tp[MAXN],f[MAXN][20];
char str[MAXN];
inline int read(){
int date=0,w=1;char c=0;
while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
return date*w;
}
void radixsort(){
for(int i=0;i<=top;i++)tax[i]=0;
for(int i=1;i<=n;i++)tax[rk[i]]++;
for(int i=1;i<=top;i++)tax[i]+=tax[i-1];
for(int i=n;i>=1;i--)sa[tax[rk[tp[i]]]--]=tp[i];
}
void suffixsort(){
top=128;
for(int i=1;i<=n;i++){
rk[i]=str[i];
tp[i]=i;
}
radixsort();
for(int w=1,p=0;p<n;top=p,w<<=1){
p=0;
for(int i=1;i<=w;i++)tp[++p]=n-w+i;
for(int i=1;i<=n;i++)if(sa[i]>w)tp[++p]=sa[i]-w;
radixsort();
swap(tp,rk);
rk[sa[1]]=p=1;
for(int i=2;i<=n;i++)
rk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+w]==tp[sa[i]+w])?p:++p;
}
}
void getheight(){
for(int i=1,j,k=0;i<=n;i++){
if(k)k--;
j=sa[rk[i]-1];
while(str[i+k]==str[j+k])k++;
height[rk[i]]=k;
}
}
void step(){
for(int i=1;i<=n;i++)f[i][0]=height[i];
for(int i=1;(1<<i)<=n;i++)
for(int j=1;j+(1<<i)-1<=n;j++)
f[j][i]=min(f[j][i-1],f[j+(1<<(i-1))][i-1]);
}
int query(int l,int r){
int x=rk[l],y=rk[r],k;
if(x>y)swap(x,y);
x++;
k=(int)log2(y-x+1);
return min(f[x][k],f[y-(1<<k)+1][k]);
}
void work(){
int ans=0;
for(int i=1;i<=m*2-n;i++){
int s=0;
for(int j=1;j<=n-m&&s<=3;){
if(str[i+j-1]!=str[m+j]){
s++;j++;
if(s>3)break;
}
else j+=query(i+j-1,m+j);
}
if(s<=3)ans++;
}
printf("%d\n",ans);
}
void init(){
scanf("%s",str+1);
m=strlen(str+1);
str[++m]='#';
scanf("%s",str+m+1);
n=strlen(str+1);
suffixsort();
getheight();
step();
}
int main(){
int t=read();
while(t--){
init();
work();
}
return 0;
}
后缀自动机:
(坑,未填。。。)
FFT:
$FFT$?
对!$FFT$!
你不禁想问,这题是字符串匹配,跟$FFT$八竿子打不着啊。。。
但是假如转换一下匹配的结果呢?
把两个串中的一个(为了方便,选择短的串)翻转,卷积的结果就是在文本串每一位置匹配的结果。
$A,C,T,G$分开算,以$A$为例:
让一个串中有$A$的位置为$1$,无$A$的位置为$0$,另一个串相反。
这样两串字符不同处会为结果的值贡献$1$ 。
对于$<=3$的结果个数和为答案。
玄妙的解法啊。。。
当然,$FFT$常数巨大,BZOJ日常卡常。
附代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#define MAXN (1<<19)
using namespace std;
const char f[4]={'A','C','G','T'};
int l1,l2;
int sum[MAXN];
char str_one[MAXN],str_two[MAXN];
inline int read(){
int date=0,w=1;char c=0;
while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
return date*w;
}
namespace FFT{
int n,l,rev[MAXN];
struct node{
double r,i;
node operator +(const node &p){return (node){r+p.r,i+p.i};}
node operator -(const node &p){return (node){r-p.r,i-p.i};}
node operator *(const node &p){return (node){r*p.r-i*p.i,r*p.i+i*p.r};}
}a[MAXN],b[MAXN],d[MAXN];
void DFT(node *a,int f){
for(int i=0;i<l;i++)d[i]=a[rev[i]];
for(int i=0;i<l;i++)a[i]=d[i];
for(int i=2;i<=l;i<<=1){
node wi=(node){cos(2.00*M_PI/i),f*sin(2.00*M_PI/i)};
for(int k=0;k<l;k+=i){
node w=(node){1.00,0.00},x,y;
for(int j=0;j<i/2;j++){
x=a[k+j];y=w*a[k+j+i/2];
a[k+j]=x+y;a[k+j+i/2]=x-y;
w=w*wi;
}
}
}
if(f==-1)
for(int i=0;i<l;i++)a[i].r/=l;
}
void init(int x){/*
for(n=l=1;l<x;l<<=1)n++;
l<<=1;
for(int i=0;i<l;i++)
for(int j=0,t=i;j<n;j++,t>>=1){rev[i]<<=1;rev[i]|=t&1;}*/
l=1;
while (l<l1+l2) l<<=1;
for (int i=0;i<l;++i)rev[i]=((i&1)?rev[i^1]+(l>>1):rev[i>>1]>>1);
}
void FFT(){
DFT(a,1);DFT(b,1);
for(int i=0;i<l;i++)a[i]=a[i]*b[i];
DFT(a,-1);
}
void DSC(int l1,int l2){
memset(rev,0,sizeof(rev));
init(l1+l2);
for(int num=0;num<4;num++){
for(int i=0;i<=l;i++)a[i].r=a[i].i=b[i].r=b[i].i=0.00;
for(int i=0;i<l1;i++)a[i].r=(str_one[i+1]!=f[num]);
for(int i=0;i<l2;i++)b[i].r=(str_two[i+1]==f[num]);
FFT();
for(int i=l2-1;i<l1;i++)sum[i]+=(int)(a[i].r+0.5);
}
}
}
void work(){
int ans=0;
for(int i=l2-1;i<l1;i++)ans+=(sum[i]<=3);
printf("%d\n",ans);
}
void init(){
memset(sum,0,sizeof(sum));
scanf("%s%s",str_one+1,str_two+1);
l1=strlen(str_one+1);l2=strlen(str_two+1);
for(int i=1;i<=(l2>>1);i++)swap(str_two[i],str_two[l2-i+1]);
FFT::DSC(l1,l2);
}
int main(){
int t=read();
while(t--){
init();
work();
}
return 0;
}