codeforces 528D. Fuzzy Search (FFT优化DP)

题目描述

传送门

题目大意:给出一个母串和一个模板串,求模板串在母串中的匹配次数。
匹配时,如果用s[i]匹配t[j],那么只要s[i-k]-s[i+k]中有字母与t[j]相同即可算作匹配成功。其中s[i]表示母串的第i位,t[j]表示模板串的第j位。

题解

如果数据范围小的话,这题就是一个DP
f[i][j]=f[i-1][j-1]&mp[i][t[j]]表示母串的第i位匹配到模板串的第j位是否可以匹配上。
其中 mp[i][j] 需要预处理,表示母串的第i位能否匹配字符 j
我们要用FFT,考虑如何构造向量
我们计算的时候将所有的字符分开考虑
对于母串来说,如果第 i 位可以匹配当前字符,则f[i]=1
对于模板串来说,如果第 i 位为当前字符,则g[i]=1
h[i] 表示到母串的第 i 位,母串[im+1,i]与模板串 [1,m] 匹配可以匹配上多少位,将模式串翻转,然后将 h[i] 表示成公式就是
h[k]=i=0mf[ki]g[i]
只有 f[ki] , g[i] 同时为1,即匹配上的时候才能产生1的贡献。
上面的式子可以用 FFT O((n+m)log(n+m)) 的时间内出解。
然后我们统计每一位所有字符的 h[i] ,如果和为 m <script type="math/tex" id="MathJax-Element-2637">m</script>则表示在这一位可以匹配上,答案+1

代码

#include<iostream>  
#include<cstdio>  
#include<algorithm>  
#include<cmath>  
#include<cstring>  
#define N 500003  
#define pi acos(-1)  
using namespace std;  
struct data{  
    double x,y;  
    data(double X=0,double Y=0) {  
        x=X,y=Y;  
    }  
}a[N*5],b[N*5];  
data operator +(data a,data b){    
    return data(a.x+b.x,a.y+b.y);    
}    
data operator -(data a,data b){    
    return data(a.x-b.x,a.y-b.y);    
}    
data operator *(data a,data b){    
    return data(a.x*b.x-a.y*b.y,a.y*b.x+a.x*b.y);    
}    
int n,m,k,n1,col[N],cl[N],mp[N][5],cost[N];  
char s[N];  
int calc(char c)  
{  
    if (c=='A') return 1;  
    if (c=='G') return 2;  
    if (c=='C') return 3;  
    if (c=='T') return 4;  
}  
void clear(data a[N])
{
    for (int i=1;i<=n1;i++) a[i].x=0,a[i].y=0;
}
void fft(data x[N],int n,int opt)  
{  
    if (n==1) return;  
    data l[n>>1],r[n>>1];  
    for (int i=0;i<n;i+=2)  
     l[i>>1]=x[i],r[i>>1]=x[i+1];  
    fft(l,n>>1,opt); fft(r,n>>1,opt);  
    data wn=data(cos(2*pi/n),sin(2*opt*pi/n));  
    data w=data(1,0),t;  
    for (int i=0;i<n>>1;i++,w=wn*w)  
     t=w*r[i],x[i]=l[i]+t,x[i+(n>>1)]=l[i]-t;  
}  
int main()  
{  
    freopen("a.in","r",stdin);
    scanf("%d%d%d",&n,&m,&k);  
    scanf("%s",s); n--;  
    for (int i=0;i<=n;i++) col[i]=calc(s[i]);  
    scanf("%s",s); m--;  
    for (int i=0;i<=m;i++) cl[i]=calc(s[m-i]);  
    for (int i=1;i<=4;i++) {  
        int nxt=-1;  
        for (int j=0;j<=n;j++){  
            if (nxt!=-1&&nxt>=j-k||col[j]==i) mp[j][i]=1;  
            if (col[j]==i)  nxt=j;  
        }  
        nxt=-1;  
        for (int j=n;j>=0;j--) {  
            if (nxt!=-1&&nxt<=j+k||col[j]==i) mp[j][i]=1;  
            if (col[j]==i)  nxt=j;  
        }  
    }  
    int tot=n+m; 
    for (n1=1;n1<=tot;n1<<=1);  
    for (int i=1;i<=4;i++) {  
        clear(b); clear(a);
        for (int j=0;j<=n;j++)   
         if (mp[j][i]) a[j].x=1;  
         else a[j].x=0;  
        for (int j=0;j<=m;j++)  
         if (cl[j]==i) b[j].x=1;  
         else b[j].x=0;
        fft(a,n1,1); fft(b,n1,1);  
        for (int j=0;j<=n1;j++) a[j]=a[j]*b[j];  
        fft(a,n1,-1);  
        for (int j=0;j<=n;j++)  
         cost[j]+=(int)(a[j].x/n1+0.5);  
    }  
    int ans=0;  
    for (int i=0;i<=n;i++)  
     if (cost[i]==m+1) ans++;  
    printf("%d\n",ans);  
 }   
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值