【模板】通配符单模式串匹配 -FFT bzoj4259: 残缺的字符串

传送门:bzoj4259


题解

带通配符的字符串匹配无法有效地用 k m p kmp kmp处理,这时大常数的 F F T FFT FFT派上了用场。

这题已经升级为一种套路/模板了,暂且引用ebola’s题解的称呼:带通配符的单模式串匹配。

设模式串为 T T T,文本串为 S S S。“*”对应0, a , b , . . . , z a,b,...,z a,b,...,z分别对应 1 , 2 , . . . , 26 1,2,...,26 1,2,...,26

为便于叙述和进行FFT,设字符串下标均从0开始。

假设在 S S S中以 x x x位置结尾的长度为 ∣ T ∣ |T| T的子串能匹配上 ∣ T ∣ |T| T,则:
(1) ∑ i = 0 ∣ T ∣ − 1 ( S x − i − T ∣ T ∣ − 1 − i ) 2 S x − i T ∣ T ∣ − 1 − i = 0 \sum\limits_{i=0}^{|T|-1}(S_{x-i}-T_{|T|-1-i})^2S_{x-i}T_{|T|-1-i}=0\tag 1 i=0T1(SxiTT1i)2SxiTT1i=0(1)

S x − i = 0 , T ∣ T ∣ − 1 − i = 0 S_{x-i}=0,T_{|T|-1-i}=0 Sxi=0,TT1i=0分别表示其中有串当前位为通配符的情况。
( S x − i − T ∣ T ∣ − 1 − i ) 2 = 0 (S_{x-i}-T_{|T|-1-i})^2=0 (SxiTT1i)2=0对应字母相同的情况。

至于取差的平方,相当于是取绝对值,这样保证了和为0的情况一定是完全匹配的(考虑如果没有取差的平方而是取了差, a b ab ab b a ba ba就完全匹配了)。

T T T串翻转后得到 R R R,则 ( 1 ) (1) (1)可以转变为卷积的形式:

(2) ∑ i = 0 ∣ R ∣ − 1 ( S x − i − R i ) 2 S x − i R i = 0 \sum\limits_{i=0}^{|R|-1}(S_{x-i}-R_i)^2S_{x-i}R_i=0\tag 2 i=0R1(SxiRi)2SxiRi=0(2)

所以设表示答案的多项式为 F F F,当 S S S以位置 i i i结尾的子串完全匹配 S S S时, F i = 0 F_i=0 Fi=0,否则 F i ≠ 0 F_i\neq0 Fi̸=0

能够发现实际上 F x F_x Fx等价于 ( 2 ) (2) (2),将 ( 2 ) (2) (2)展开:
F x = ∑ i = 0 ∣ R ∣ − 1 ( S x − i 3 R i − 2 S x − i 2 R i 2 + S x − i R i 3 ) F_x=\sum\limits_{i=0}^{|R|-1}(S_{x-i}^3R_i-2S_{x-i}^2R_i^2+S_{x-i}R_i^3) Fx=i=0R1(Sxi3Ri2Sxi2Ri2+SxiRi3)

做7遍 F F T FFT FFT即可。


代码

#include
#define db double
#define RI register
using namespace std;
const int N=1e6+10,M=3e5+2;
const db pi=acos(-1);

int n,m,L,len,rv[N],ans[M],tot,A[M],B[M];
char s[2][M];

struct cc{
	db r,i;
	cc(){r=i=0;};
	cc(db r_,db i_){r=r_;i=i_;}
	cc operator +(const cc&ky){return cc(r+ky.r,i+ky.i);}
	cc operator -(const cc&ky){return cc(r-ky.r,i-ky.i);}
	cc operator *(const cc&ky){return cc(r*ky.r-i*ky.i,r*ky.i+i*ky.r);}
	cc operator /(const int&ky){return cc(r/(db)ky,i/(db)ky);}
	inline cc conj(){return cc(r,-i);}
}a[N],b[N],f[N];

inline void FFT(cc *e,int ptr)
{
	RI int i,j,k;cc bs,ori,ix,iy;
	for(i=1;i<len;++i) if(i<rv[i]) swap(e[i],e[rv[i]]);
	for(i=1;i<len;i<<=1){
		ori=cc(cos(pi/i),ptr*sin(pi/i));
		for(j=0;j<len;j+=(i<<1)){
			bs=cc(1,0);
			for(k=0;k<i;++k,bs=bs*ori){
				ix=e[j+k];iy=bs*e[i+j+k];
				e[j+k]=ix+iy;e[i+j+k]=ix-iy;
			}
		}
	}
	if(ptr==1) return;
	for(i=0;i<len;++i) e[i]=e[i]/len;
}

int main(){
	RI int i,j,k,t;
	scanf("%d%d%s%s",&m,&n,s[0],s[1]);
	for(i=0;i<m;++i) A[m-1-i]= s[0][i]=='*'?0:(s[0][i]-'a'+1);
	for(i=0;i<n;++i) B[i]= s[1][i]=='*'?0:(s[1][i]-'a'+1);
	len=1;L=0;
	for(;len<=n;len<<=1) L++;
	for(i=1;i<len;++i) rv[i]=(rv[i>>1]>>1)|((i&1)<<(L-1));
   
    for(i=0;i<len;++i) a[i]=b[i]=cc(0,0);
    for(i=0;i<m;++i) a[i].r=A[i]*A[i]*A[i];
    for(i=0;i<n;++i) b[i].r=B[i];
    FFT(a,1);FFT(b,1);
    for(i=0;i<len;++i) f[i]=a[i]*b[i];
    
    for(i=0;i<len;++i) a[i]=b[i]=cc(0,0);
    for(i=0;i<m;++i) a[i].r=A[i];
    for(i=0;i<n;++i) b[i].r=B[i]*B[i]*B[i];
    FFT(a,1);FFT(b,1);
    for(i=0;i<len;++i) f[i]=f[i]+a[i]*b[i];
    
    for(i=0;i<len;++i) a[i]=b[i]=cc(0,0);
    for(i=0;i<m;++i) a[i].r=A[i]*A[i];
    for(i=0;i<n;++i) b[i].r=B[i]*B[i];
    FFT(a,1);FFT(b,1);
    for(i=0;i<len;++i) f[i]=f[i]-a[i]*b[i]*cc(2,0);

    FFT(f,-1);
	for(i=m-1;i<n;++i) 
	  if(int(f[i].r+0.5)==0) 
	    ans[tot++]=i+2-m;
	printf("%d\n",tot);
	for(i=0;i<tot;++i) printf("%d ",ans[i]);
	return 0;
}

你可能感兴趣的:(---多项式---,FFT,NTT)