bzoj 4503: 兩個串【腦洞+FFT】
阿新 • • 發佈:2018-11-28
真實腦洞題
因為萬用字元所以導致t串實際有指數級別個,任何字串相關演算法都沒有用
考慮一個新的匹配方法:設a串(模板串)長為n,從m串的i位置開始匹配:\( \sum_{i=0}^{n-1}(a[j]-b[i+j])^2a[j] \)
這個東西只有在從i開始的長為n的a串子串與b串完全匹配的時候才為0,因為首先如果兩個字元相同,差的平方和才為0,令t中的'?'值為0,這樣某一位為0就是這一位的字元匹配上或者a串的這一位通配
然後考慮優化這個n^2的東西
\[ c[i]=\sum_{i=0}^{n-1}(a[j]-b[i+j])^2a[j] \]
\[ =\sum_{i=0}^{n-1}a[j]^3+a[j]b[i+j]^2-2a[j]^2b[i+j] \]
\[ =\sum_{i=0}^{n-1}a[j]^3+\sum_{i=0}^{n-1}a[j]b[i+j]^2-2\sum_{i=0}^{n-1}a[j]^2b[i+j] \]
前面的是常數,所以我們只需要考慮快速計算形如\( \sum_{i=0}^{n-1}a[j]b[i+j] \) 的東西即可,設na nb分別是a b陣列倒過來(也就是reverse一下)
\[ \sum_{i=0}^{n-1}a[j]b[i+j] \]
\[ =\sum_{i=0}^{n-i-1}a[j]b[i+j]+\sum_{i=n-i}^{n-1}a[j]b[i+j] \]
\[ =\sum_{i=0}^{n-i-1}a[j]nb[n-i-1-j]+\sum_{i=0}^{i-1}a[n-i+j]b[n+j] \]
\[ =\sum_{i=0}^{n-i-1}a[j]nb[(n-i-1)-j]+\sum_{i=0}^{i-1}na[(i-1)-j]b[n+j] \]
這樣就兩部分卷積可以用FFT優化了
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> using namespace std; const int N=500005; int n,m,bt,lm,re[N],tot; double ans[N],sm; char s[N],t[N]; struct cd { double a,b; cd(double A=0,double B=0) { a=A,b=B; } cd operator + (const cd &x) const { return cd(a+x.a,b+x.b); } cd operator - (const cd &x) const { return cd(a-x.a,b-x.b); } cd operator * (const cd &x) const { return cd(a*x.a-b*x.b,a*x.b+b*x.a); } }a[N],b[N],a2[N],b2[N],na[N],nb[N],na2[N],nb2[N]; void dft(cd a[],int f) { for(int i=0;i<lm;i++) if(i<re[i]) swap(a[i],a[re[i]]); for(int i=1;i<lm;i<<=1) { cd wi=cd(cos(M_PI/i),f*sin(M_PI/i)); for(int k=0;k<lm;k+=(i<<1)) { cd w=cd(1,0),x,y; for(int j=0;j<i;j++) { x=a[j+k],y=w*a[i+j+k]; a[j+k]=x+y,a[i+j+k]=x-y; w=w*wi; } } } if(f==-1) for(int i=0;i<lm;i++) a[i].a/=lm; } void fft(cd a[],cd b[]) { dft(a,1); dft(b,1); for(int i=0;i<lm;i++) a[i]=a[i]*b[i]; dft(a,-1); } int main() { scanf("%s%s",s,t); m=strlen(s),n=strlen(t); for(int i=0;i<n;i++) a[i].a=(t[i]=='?')?0:t[i]-'a'+1,a2[i].a=a[i].a*a[i].a,sm+=a[i].a*a[i].a*a[i].a; for(int i=0;i<m;i++) b[i].a=s[i]-'a'+1,b2[i].a=b[i].a*b[i].a; for(int i=0;i<m;i++) na[i]=a[m-i-1],nb[i]=b[m-i-1],na2[i]=a2[m-i-1],nb2[i]=b2[m-i-1]; for(bt=0;(1<<bt)<=2*m;bt++); lm=1<<bt; for(int i=0;i<lm;i++) re[i]=(re[i>>1]>>1)|((i&1)<<(bt-1)); fft(a,nb2); fft(b2,na); fft(a2,nb); fft(b,na2); for(int i=0;i<=m-n;i++) { ans[i]=sm+(int)(a[m-i-1].a+0.5)-2*(int)(a2[m-i-1].a+0.5); if(i>0) ans[i]+=(int)(b2[i-1].a+0.5)-2*(int)(b[i-1].a+0.5); tot+=(ans[i]==0); } printf("%d\n",tot); for(int i=0;i<=m-n;i++) if(ans[i]==0) printf("%d ",i); return 0; }