CF528D Fuzzy Search
k = 0 k=0 k=0时
题目保证只有四个字母 " A T G C " "ATGC" "ATGC",暗示我们可以先分成4种情况处理,最后加起来。
比如说文本串 S = " T A A T G C A " S="TAATGCA" S="TAATGCA"和模板串 T = " A A T G C " T="AATGC" T="AATGC"
先处理 A A A的情况:
S = " # A A # # # A " S="\#AA\#\#\#A" S="#AA###A"
T = " A A # # # " T="AA\#\#\#" T="AA###"
以 S ( 5 ) S(5) S(5)(下标以0开始)结尾的长度为5的子串和模板串匹配的长度为2
同理,处理 T T T的情况,以 S ( 5 ) S(5) S(5)结尾的长度为5的子串和模板串匹配的长度为1
处理 C C C的情况,以 S ( 5 ) S(5) S(5)结尾的长度为5的子串和模板串匹配的长度为1
处理 G G G的情况,以 S ( 5 ) S(5) S(5)结尾的长度为5的子串和模板串匹配的长度为1
加起来后可知,以 S ( 5 ) S(5) S(5)结尾的长度为5的子串和模板串匹配的长度为5,即以 S ( 5 ) S(5) S(5)结尾的长度为5的子串和模板串完全匹配
k > 0 k>0 k>0时,可以理解为将文本串中的字母 C C C(当前在处理字母 c c c的情况)往两边扩散 k k k个距离。
比如 k = 1 k=1 k=1,此时先处理 A A A的情况:
S = " A A A A # # A " S="AAAA\#\#A" S="AAAA##A"
T = " A A # # # " T="AA\#\#\#" T="AA###"
以 S ( 5 ) S(5) S(5)(下标以0开始)结尾的长度为5的子串和模板串匹配的长度为2
当前处理字符 c c c时,接下来就是构造多项式 f = ∑ i = 0 n − 1 [ S [ i ] = = ′ c ′ ] ∗ x i f=\sum_{i=0}^{n-1}[S[i]=='c']*x^i f=∑i=0n−1[S[i]==′c′]∗xi,多项式 g = ∑ i = 0 m − 1 [ T [ m − 1 − i ] = = ′ c ′ ] ∗ x i g=\sum_{i=0}^{m-1}[T[m-1-i]=='c']*x^i g=∑i=0m−1[T[m−1−i]==′c′]∗xi,然后 a = f ∗ s a=f*s a=f∗s, a ( d ) = ∑ i + j = d [ S [ i ] = = ′ c ′ & & T [ m − 1 − j ] = = ′ c ′ ] x d a(d)=\sum_{i+j=d}{[S[i]=='c'\&\&T[m-1-j]=='c']x^d} a(d)=∑i+j=d[S[i]==′c′&&T[m−1−j]==′c′]xd就表示以 S ( i ) S(i) S(i)结尾的长度为 m − 1 m-1 m−1的子串和模板串匹配的长度。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=(1<<19)+7,mod=998244353;
inline int qpow(int x,int y) {
int res(1);
while(y) {
if(y&1) res=1LL*res*x%mod;
x=1LL*x*x%mod;
y>>=1;
}
return res;
}
int r[maxn];
inline void ntt(int *x,int lim,int opt) {
int i,j,k,m,gn,g,tmp;
for(i=0; i<lim; ++i)
if(i<r[i]) swap(x[i],x[r[i]]);
for(m=2; m<=lim; m<<=1) {
k=m>>1;
gn=qpow(3,(mod-1)/m);
for(i=0; i<lim; i+=m) {
g=1;
for(j=0; j<k; ++j,g=1LL*g*gn%mod) {
tmp=1LL*x[i+j+k]*g%mod;
x[i+j+k]=(x[i+j]-tmp+mod)%mod;
x[i+j]=(x[i+j]+tmp)%mod;
}
}
}
if(opt==-1) {
reverse(x+1,x+lim);
int inv=qpow(lim,mod-2);
for(i=0; i<lim; ++i) x[i]=1LL*x[i]*inv%mod;
}
}
int A[maxn],B[maxn],cnt[maxn],n,m,k,lim(1);
char a[maxn],b[maxn];
void solve(char c) {
for(int i=0;i<lim;++i) A[i]=B[i]=0;
for(int i=0,last=-1e9;i<n;++i) {
if(a[i]==c) last=i;
if(i-last<=k) A[i]=1;
}
for(int i=n-1,last=1e9;~i;--i) {
if(a[i]==c) last=i;
if(last-i<=k) A[i]=1;
}
for(int i=0;i<m;++i) B[i]=(b[m-1-i]==c);
ntt(A,lim,1),ntt(B,lim,1);
for(int i=0; i<lim; ++i) A[i]=1LL*A[i]*B[i]%mod;
ntt(A,lim,-1);
for(int i=0;i<lim;++i) cnt[i]+=A[i];
}
int main() {
scanf("%d%d%d",&n,&m,&k);
scanf("%s%s",a,b);
while(lim<(n<<1)) lim<<=1;
for(int i=0; i<lim; ++i) r[i]=(i&1)*(lim>>1)+(r[i>>1] >> 1);
for(int i=0;i<4;++i) solve("ATGC"[i]);
int ans(0);
for(int i=m-1;i<lim;++i) ans+=(cnt[i]==m);
cout<<ans<<'\n';
return 0;
}
P4173 残缺的字符串
文本串为 A ( 长 度 为 n ) A(长度为n) A(长度为n),模式串为 B ( 长 度 为 m ) B(长度为m) B(长度为m)
不考虑通配符的时候,可以用完全匹配函数可以是 P ( x ) = ∑ i = 0 m − 1 [ B ( i ) − A ( x − m + 1 + i ) ] 2 {P(x)=\sum_{i=0}^{m-1}{[B(i)-A(x-m+1+i)\ ]^2}} P(x)=∑i=0m−1[B(i)−A(x−m+1+i) ]2
将通配符赋值为 0 0 0,那么有完全匹配函数 P ( x ) = ∑ i = 0 m − 1 [ B ( i ) − A ( x − m + 1 + i ) ] 2 ∗ A ( x − m + 1 + i ) ∗ B ( i ) {P(x)=\sum_{i=0}^{m-1}{[B(i)-A(x-m+1+i)\ ]^2*A(x-m+1+i)*B(i)}} P(x)=∑i=0m−1[B(i)−A(x−m+1+i) ]2∗A(x−m+1+i)∗B(i)
老套路: S ( i ) = B ( m − 1 − i ) , B ( i ) = S ( m − 1 − i ) S(i)=B(m-1-i),B(i)=S(m-1-i) S(i)=B(m−1−i),B(i)=S(m−1−i)
那么有完全匹配函数 P ( x ) = ∑ i = 0 m − 1 S ( m − 1 − i ) 3 ∗ A ( x − m + 1 + i ) + ∑ i = 0 m − 1 A ( x − m + 1 + i ) 3 ∗ S ( m − 1 − i ) − 2 ∗ ∑ i = 0 m − 1 A ( x − m + 1 + i ) 2 ∗ S ( m − 1 − i ) 2 {P(x)=\sum_{i=0}^{m-1}{S(m-1-i)^3*A(x-m+1+i)}+\sum_{i=0}^{m-1}{A(x-m+1+i)^3*S(m-1-i)}-2*\sum_{i=0}^{m-1}{A(x-m+1+i)^2*S(m-1-i)^2}} P(x)=∑i=0m−1S(m−1−i)3∗A(x−m+1+i)+∑i=0m−1A(x−m+1+i)3∗S(m−1−i)−2∗∑i=0m−1A(x−m+1+i)2∗S(m−1−i)2
P ( x ) = ∑ i + j = x S ( i ) 3 ∗ A ( j ) + ∑ i + j = x A ( i ) 3 ∗ S ( j ) − 2 ∗ ∑ i + j = x A ( i ) 2 ∗ S ( j ) 2 {P(x)=\sum_{i+j=x}{S(i)^3*A(j)}+\sum_{i+j=x}{A(i)^3*S(j)}-2*\sum_{i+j=x}{A(i)^2*S(j)^2}} P(x)=∑i+j=xS(i)3∗A(j)+∑i+j=xA(i)3∗S(j)−2∗∑i+j=xA(i)2∗S(j)2
做7次 F F T FFT FFT就好了,浮点数精度可能会有问题, p ( x ) < 1 p(x)<1 p(x)<1就可以看作 0 0 0了,我看有的人是 p ( x ) < 1 e − 7 p(x)<1e-7 p(x)<1e−7,而我改成 p ( x ) < 1 e − 6 p(x)<1e-6 p(x)<1e−6都wa。
用 N T T NTT NTT涉及取余( p ( x ) p(x) p(x)会大于 m o d ( 998244353 ) mod(998244353) mod(998244353)),可能会影响结果?我试了一下只有65分。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=(1<<20)+7;
const double PI=acos(-1.0);
struct Complex{
double x,y;
Complex(double _x=0.0,double _y=0.0) {
x=_x;
y=_y;
}
Complex operator-(const Complex &b) const {
return Complex(x-b.x,y-b.y);
}
Complex operator+(const Complex &b) const {
return Complex(x+b.x,y+b.y);
}
Complex operator*(const Complex &b) const {
return Complex(x*b.x-y*b.y,x*b.y+y*b.x);
}
};
int r[maxn];
inline void fft(Complex *y,int len,int opt) {
for(int i=0;i<len;++i)
if(i<r[i]) swap(y[i],y[r[i]]);
for(int h=2;h<=len;h<<=1) {
Complex wn(cos(2*PI/h),sin(opt*2*PI/h));
for(int j=0;j<len;j+=h) {
Complex w(1,0);
for(int k=j;k<j+h/2;++k) {
Complex tmp=w*y[k+h/2];
y[k+h/2]=y[k]-tmp;
y[k]=y[k]+tmp;
w=w*wn;
}
}
}
if(opt==-1) {
for(int i=0;i<len;++i) y[i].x/=len;
}
}
Complex A[maxn],B[maxn],p[maxn];
double x[maxn],y[maxn];
char a[maxn/2],b[maxn/2];
int main() {
int n,m,k,lim(1);
scanf("%d%d",&m,&n);
scanf("%s%s",b,a);
while(lim<(n<<1)) lim<<=1;
for(int i=0; i<lim; ++i) r[i]=(i&1)*(lim>>1)+(r[i>>1] >> 1);
for(int i=0;i<n;++i) x[i]=(a[i]!='*')?a[i]-'a'+1:0,A[i]=Complex(x[i]*x[i]*x[i],0);
for(int i=0;i<m;++i) y[i]=(b[m-1-i]!='*')?b[m-1-i]-'a'+1:0,B[i]=Complex(y[i],0);
fft(A,lim,1),fft(B,lim,1);
for(int i=0;i<lim;++i) {
p[i]=p[i]+A[i]*B[i];
A[i]=Complex(x[i],0);
B[i]=Complex(y[i]*y[i]*y[i],0);
}
fft(A,lim,1),fft(B,lim,1);
for(int i=0;i<lim;++i) {
p[i]=p[i]+A[i]*B[i];
A[i]=Complex(x[i]*x[i],0);
B[i]=Complex(y[i]*y[i],0);
}
fft(A,lim,1),fft(B,lim,1);
for(int i=0;i<lim;++i) p[i]=p[i]-A[i]*B[i]*Complex(2,0);
fft(p,lim,-1);
vector<int>ans;
for(int i=m-1;i<n;++i) {
if(fabs(p[i].x)<1) ans.emplace_back(i-m+2);
}
cout<<ans.size()<<'\n';
for(int i:ans) cout<<i<<' ';
return 0;
}