CodeForces 528D Fuzzy Search(FFT)

原题地址http://codeforces.com/problemset/problem/528/D

题意:
给给定两个字符串 S,T(字符集大小为4:A ,G ,C ,T),给定阀值 k,
定义T串在S串某位置匹配,当且仅当T串任意位置的这个字符所对应的母串的位置的左右k个字符之内至少有一个与这个字符相同的。
求T串在S串中的匹配次数。
例如,对于S=AGCAATTCAT,T=ACAT,k=1,匹配次数为3,如图所示:

这里写图片描述

数据范围
1 ≤ |T| ≤ |S| ≤ 200 000,0 ≤ k ≤ 200 000

题解:

和上一题相似,把“匹配”转化为卷积的形式。
但是,有一个问题在于匹配的定义,有阀值这个东西让我们不能直接构造出合适的表示方法。
要使匹配是准确的,唯独是一个位置可以表示左右k个位置的字符种类。

那么,如果我们只考虑一种字符的匹配,可以想到:
对于S串,构造序列f使得当i的左右k位存在该字符时,第i位置为1,否则第i位置为0。
对于T串,构造序列g使得当i位是该字符时,第i位置为1,否则第i位置为0。
那么对于这个字符的匹配个数就是:
ansi=j=0|T|1fijgj

于是可以四种字符分别跑一遍,加起来,ans为|T|的就匹配上了。

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#define LD long double
using namespace std;
const int N=200005;
const int MXN=524288+1000;
const long double Pi=acos(-1);
char s[N],t[N];
int S,T,k,cnt[5],num[128],len,p,R[MXN],ans[MXN];
struct Virt
{
    long double r,i;
    Virt(){}
    Virt(long double r,long double i):r(r),i(i){}
    Virt operator+(const Virt &A){return Virt(r+A.r,i+A.i);}
    Virt operator-(const Virt &A){return Virt(r-A.r,i-A.i);}
    Virt operator*(const Virt &A){return Virt(r*A.r-i*A.i,r*A.i+i*A.r);}
}omg[MXN],_omg[MXN],a[MXN],b[MXN],c[MXN];
bool ok[MXN][5];
void FFT(Virt *x,int opt)
{
    for(int i=0;i<len;i++) if(i<R[i]) swap(x[i],x[R[i]]);
    Virt *w; if(opt==1) w=omg; else w=_omg;
    for(int m=2;m<=len;m<<=1)
    {
        int l=m>>1;
        for(int j=0;j<len;j+=m)
        for(int i=0;i<l;i++)
        {
            Virt y=w[len/m*i]*x[i+j+l];
            x[i+j+l]=x[i+j]-y;
            x[i+j]=x[i+j]+y;
        }
    }
    if(opt==-1) for(int i=0;i<len;i++) x[i].r=(LD)x[i].r/(LD)len;
}
int main()
{
    num['A']=0,num['G']=1,num['C']=2,num['T']=3;
    scanf("%d%d%d",&S,&T,&k); scanf("%s%s",s,t);
    cnt[num[s[0]]]=1;
    for(int i=0,p1=0,p2=0;i<S;i++)
    {
        while(p1<i-k){cnt[num[s[p1]]]--; p1++;}
        while(p2<i+k&&p2<S-1){p2++; cnt[num[s[p2]]]++;}
        for(int j=0;j<4;j++) if(cnt[j]>0) ok[S-i-1][j]=1;
    }
    for(len=1,p=0;len<(S+T);len<<=1,p++);
    R[0]=0; for(int i=1;i<len;i++) R[i]=(R[i>>1]>>1)|((i&1)<<(p-1));
    for(int i=0;i<len;i++) omg[i]=Virt(cos((LD)2*Pi/(LD)len*i),sin((LD)2*Pi/(LD)len*i)),_omg[i]=Virt(omg[i].r,-omg[i].i);
    for(int c=0;c<4;c++)
    {
        for(int i=0;i<len;i++) a[i]=Virt(ok[i][c],0),b[i]=Virt((i<T&&(num[t[i]]==c)),0);
        FFT(a,1); FFT(b,1);
        for(int i=0;i<len;i++) a[i]=a[i]*b[i];
        FFT(a,-1);
        for(int i=0;i<len;i++) ans[i]+=(int)(a[i].r+0.5);
    }
    int ret=0;
    for(int i=T-1;i<S;i++) if(ans[i]==T) ret++;
    printf("%d\n",ret); 
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值