题意:
考虑模板串B和给定串A,给定K,对于模板串上给定位置j的字符,如果能在给定串上i左右K个字符内找到相同字符,则说可以匹配。
问有多少匹配。
解法:
考虑对于每一种字符分开求。
对于当前字符ch,将B串中为此字符的位置标为1其他位置为0,将A串中所有可以匹配ch的位置标为1,其他为0,这样
记$c_i$表示以 i 为起点字符ch可以匹配到几个。
$$c_i = sum_{ 0 leq j<m} { b_j a_{i+j} }$$
$$c_i = sum_{0 leq k leq i+m} {revb_k a_{m+i-k}}$$
卷积即可。
#include <bits/stdc++.h> #define PI acos(-1) const int N = 200010; using namespace std; struct EX { double real,i; EX operator+(const EX tmp)const{return (EX){real+tmp.real, i+tmp.i};}; EX operator-(const EX tmp)const{return (EX){real-tmp.real, i-tmp.i};}; EX operator*(const EX tmp)const{return (EX){real*tmp.real - i*tmp.i, real*tmp.i + i*tmp.real};}; }; int R[N<<2]; void DFT(EX a[],int n,int tp_k) { for(int i=0;i<n;i++) if(i<R[i]) swap(a[i],a[R[i]]); for(int d=1;d<n;d<<=1) { EX wn = (EX){cos(PI/d), sin(PI/d)*tp_k}; for(int i=0;i<n;i += (d<<1)) { EX wt = (EX){1,0}; for(int k=0;k<d;k++, wt = wt*wn) { EX A0 = a[i+k], A1 = wt * a[i+k+d]; a[i+k] = A0+A1; a[i+k+d] = A0-A1; } } } if(tp_k==-1) for(int i=0;i<n;i++) a[i] = (EX){a[i].real/n, a[i].i/n}; } int n,m,K; EX A[N<<2],B[N<<2],C[N<<2]; char S[N],S2[N]; bool del[N]; void calc(char ch,int tot) { memset(B,0,sizeof(B)); memset(A,0,sizeof(A)); int cnt_S2 = 0; for(int i=0;i<m;i++) if(S2[i]==ch) B[m-i] = (EX){1,0}, cnt_S2++; int tp = -1; for(int i=0;i<n;i++) { if(S[i]==ch) tp = i; if(tp!=-1 && i-tp<=K) A[i] = (EX){1,0}; } tp = -1; for(int i=n-1;i>=0;i--) { if(S[i]==ch) tp = i; if(tp!=-1 && tp-i<=K) A[i] = (EX){1,0}; } DFT(A,tot,1); DFT(B,tot,1); for(int i=0;i<tot;i++) C[i] = A[i]*B[i]; DFT(C,tot,-1); for(int i=0;i<n;i++) { int tmp = C[i+m].real+0.5; if(tmp<cnt_S2) del[i] = 1; } } int main() { scanf("%d%d%d",&n,&m,&K); scanf("%s%s",S,S2); int L = 0,tot; while((1<<L)<n+m) L++; tot = (1<<L); for(int i=1;i<tot;i++) R[i]=(R[i>>1]>>1)|((i&1)<<(L-1)); calc('A',tot); calc('T',tot); calc('C',tot); calc('G',tot); int ans = 0; for(int i=0;i<=n-m;i++) if(!del[i]) ans++; printf("%d ",ans); return 0; }