题意:
很久很久以前,在你刚刚学习字符串匹配的时候,有两个仅包含小写字母的字符串A和B,其中A串长度为m,B串长度为n。可当你现在再次碰到这两个串时,这两个串已经老化了,每个串都有不同程度的残缺。
你想对这两个串重新进行匹配,其中A为模板串,那么现在问题来了,请回答,对于B的每一个位置i,从这个位置开始连续m个字符形成的子串是否可能与A串完全匹配?
数据范围:n,m<=3e5
解法:
先介绍FFT进行字符串匹配的原理:
假设A和B是数字,那么A=B可以等价为A-B=0,
当A和B是字符串的时候,A=B似乎可以变为: ∑ i = 0 n − 1 ( A [ i ] − b [ i ] ) = 0 \sum_{i=0}^{n-1}(A[i]-b[i])=0 ∑i=0n−1(A[i]−b[i])=0
但是求和存在正负相消,因此将每个位置的计算变成绝对值: ∑ i = 0 n − 1 ∣ A [ i ] − B [ i ] ∣ = 0 \sum_{i=0}^{n-1}|A[i]-B[i]|=0 ∑i=0n−1∣A[i]−B[i]∣=0
绝对值不好算,可以平方一下把式子变成: ∑ i = 0 n − 1 ( A [ i ] − B [ i ] ) 2 = 0 \sum_{i=0}^{n-1}(A[i]-B[i])^2=0 ∑i=0n−1(A[i]−B[i])2=0
展开变为: ∑ i = 0 n − 1 ( A [ i ] 2 + B [ i ] 2 ) − 2 ∑ i = 0 n − 1 A [ i ] ∗ B [ i ] = 0 \sum_{i=0}^{n-1}(A[i]^2+B[i]^2)-2\sum_{i=0}^{n-1}A[i]*B[i]=0 ∑i=0n−1(A[i]2+B[i]2)−2∑i=0n−1A[i]∗B[i]=0
左边的式子可以预处理前缀和算,
右边的式子可以通过将B串翻转为B’,变为: 2 ∑ i = 0 n − 1 A [ i ] ∗ B ′ [ n − 1 − i ] 2\sum_{i=0}^{n-1}A[i]*B'[n-1-i] 2∑i=0n−1A[i]∗B′[n−1−i]
是卷积形式,可以用FFT计算,复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
-----分割线-----
观察复杂度,显然用KMP算法匹配效率更高,那用FFT匹配有啥用呢?
当字符串中存在通配符时,KMP算法没办法进行匹配,这时候就得用FFT了。
对于A[i]和B[i],如果他们能匹配,那么一定满足下列条件之一:
1.A[i]是通配符
2.B[i]是通配符
3.A[i]=B[i]
我们原本要满足的是 ( A [ i ] − B [ i ] ) 2 = 0 (A[i]-B[i])^2=0 (A[i]−B[i])2=0,即条件三,
现在多了通配符的条件,可以设通配符为0,
那么只需要把式子变为 ( A [ i ] − B [ i ] ) 2 ∗ A [ i ] ∗ B [ i ] = 0 (A[i]-B[i])^2*A[i]*B[i]=0 (A[i]−B[i])2∗A[i]∗B[i]=0即可,
用0乘任何数都等于0巧妙的将三个条件逻辑与了起来。
那么串A=串B的式子就变为: ∑ i = 0 n − 1 ( A [ i ] − B [ i ] ) 2 ∗ A [ i ] ∗ B [ i ] \sum_{i=0}^{n-1}(A[i]-B[i])^2*A[i]*B[i] ∑i=0n−1(A[i]−B[i])2∗A[i]∗B[i]
将式子展开变为: ∑ i = 0 n − 1 ( A [ i ] 3 ∗ B [ i ] ) − 2 ∑ i = 0 n − 1 ( A [ i ] 2 ∗ B [ i ] 2 ) + ∑ i = 0 n − 1 ( A [ i ] ∗ B [ i ] 3 ) \sum_{i=0}^{n-1}(A[i]^3*B[i])-2\sum_{i=0}^{n-1}(A[i]^2*B[i]^2)+\sum_{i=0}^{n-1}(A[i]*B[i]^3) ∑i=0n−1(A[i]3∗B[i])−2∑i=0n−1(A[i]2∗B[i]2)+∑i=0n−1(A[i]∗B[i]3)
翻转B串变为: ∑ i = 0 n − 1 ( A [ i ] 3 ∗ B ′ [ n − 1 − i ] ) − 2 ∑ i = 0 n − 1 ( A [ i ] 2 ∗ B ′ [ n − 1 − i ] 2 ) + ∑ i = 0 n − 1 ( A [ i ] ∗ B ′ [ n − 1 − i ] 3 ) \sum_{i=0}^{n-1}(A[i]^3*B'[n-1-i])-2\sum_{i=0}^{n-1}(A[i]^2*B'[n-1-i]^2)+\sum_{i=0}^{n-1}(A[i]*B'[n-1-i]^3) ∑i=0n−1(A[i]3∗B′[n−1−i])−2∑i=0n−1(A[i]2∗B′[n−1−i]2)+∑i=0n−1(A[i]∗B′[n−1−i]3)
令 C ( n ) = ∑ i = 0 n ( A [ i ] 3 ∗ B ′ [ n − i ] ) − 2 ∑ i = 0 n ( A [ i ] 2 ∗ B ′ [ n − i ] 2 ) + ∑ i = 0 n ( A [ i ] ∗ B ′ [ n − i ] 3 ) C(n)=\sum_{i=0}^{n}(A[i]^3*B'[n-i])-2\sum_{i=0}^{n}(A[i]^2*B'[n-i]^2)+\sum_{i=0}^{n}(A[i]*B'[n-i]^3) C(n)=∑i=0n(A[i]3∗B′[n−i])−2∑i=0n(A[i]2∗B′[n−i]2)+∑i=0n(A[i]∗B′[n−i]3)
FFT计算出C[],然后逐个判断C[i]是否为0即可。
要用eps判断,因为是浮点数。
-----分割线-----
一开始没想懂为什么只需要判断C(i)是否为0,c(i-m)不用管,手玩了一组数据就懂了:
翻转,空余处补零,卷积的时候前面部分会和0消掉,所以直接判断C(i)就行了。
-----分割线-----
学自:https://www.luogu.com.cn/blog/cqbzllsw/bzoj-4259-can-que-di-zi-fu-chuan
code:
#include<bits/stdc++.h>
using namespace std;
const double P=acos(-1.0);
struct CC{//复数
double x,y;
CC(double xx=0,double yy=0){x=xx,y=yy;}
CC operator+(const CC &a)const{return CC(x+a.x,y+a.y);}
CC operator-(const CC &a)const{return CC(x-a.x,y-a.y);}
CC operator*(const CC &a)const{return CC(x*a.x-y*a.y,x*a.y+y*a.x);}
CC operator*(const double a)const{return CC(x*a,y*a);}
};
void change(CC y[],int len){
for(int i=1,j=len/2;i<len-1;i++){
if(i<j)swap(y[i],y[j]);
int k=len/2;
while(j>=k){
j-=k;
k/=2;
}
if(j<k)j+=k;
}
}
void fft(CC y[],int len,int on){//on为1或者-1,-1的时候表示逆变换
change(y,len);
for(int h=2;h<=len;h<<=1){
CC wn(cos(-on*2*P/h),sin(-on*2*P/h));
for(int j=0;j<len;j+=h){
CC w(1,0);
for(int k=j;k<j+h/2;k++){
CC u=y[k];
CC t=w*y[k+h/2];
y[k]=u+t;
y[k+h/2]=u-t;
w=w*wn;
}
}
}
if(on==-1){
for(int i=0;i<len;i++){
y[i].x/=len;
}
}
}
//
const int maxm=2e6+5;
char s[maxm];
char t[maxm];
int a[maxm];
int b[maxm];
CC A[maxm];
CC B[maxm];
CC C[maxm];
int n,m;
signed main(){
scanf("%d%d",&m,&n);
scanf("%s%s",t,s);
for(int i=0;i<n;i++)a[i]=(s[i]=='*'?0:s[i]-'a'+1);
for(int i=0;i<m;i++)b[i]=(t[i]=='*'?0:t[i]-'a'+1);
reverse(b,b+m);
//
int len=1;
while(len<n*2)len<<=1;
//
for(int i=0;i<n;i++)A[i]=CC(a[i]*a[i]*a[i],0);
for(int i=n;i<len;i++)A[i]=CC(0,0);
for(int i=0;i<m;i++)B[i]=CC(b[i],0);
for(int i=m;i<len;i++)B[i]=CC(0,0);
fft(A,len,1);
fft(B,len,1);
for(int i=0;i<len;i++)C[i]=C[i]+A[i]*B[i];
//
for(int i=0;i<n;i++)A[i]=CC(a[i]*a[i],0);
for(int i=n;i<len;i++)A[i]=CC(0,0);
for(int i=0;i<m;i++)B[i]=CC(b[i]*b[i],0);
for(int i=m;i<len;i++)B[i]=CC(0,0);
fft(A,len,1);
fft(B,len,1);
for(int i=0;i<len;i++)C[i]=C[i]-A[i]*B[i]*2.0;
//
for(int i=0;i<n;i++)A[i]=CC(a[i],0);
for(int i=n;i<len;i++)A[i]=CC(0,0);
for(int i=0;i<m;i++)B[i]=CC(b[i]*b[i]*b[i],0);
for(int i=m;i<len;i++)B[i]=CC(0,0);
fft(A,len,1);
fft(B,len,1);
for(int i=0;i<len;i++)C[i]=C[i]+A[i]*B[i];
//
fft(C,len,-1);
vector<int>ans;
for(int i=m-1;i<n;i++){
if(fabs(C[i].x)<0.5){
ans.push_back(i-m+2);
}
}
printf("%d\n",ans.size());
for(auto i:ans){
printf("%d ",i);
}
return 0;
}