【学习笔记】摸鱼之 -
FFT的例题
- 我刚学
FFT不到
1天,做了几道题目来回顾一下(大佬勿喷
- 如果你不会
FFT,一个不错的博客
Sol
-
Fj=∑i=1j−1(i−j)2qi×qj−∑i=j+1n(i−j)2qi×qj
- 那么
Ej=qjFj=∑i=1j−1(i−j)2qj−∑i=j+1n(i−j)2qj
- 我们设
fi=qi,gi=i21
- 那么
Ej=∑i=0j−1fi×gj−i−∑i=j+1nfi×gj−i
- 我们再设
hi为
fi反向后的值
- 那么
Ej=∑i=0j−1fi×gj−i−∑i=0j−1hi×gj−i
- 这样子就成了卷积形式用
FFT即可。
Code
#include <iostream>
#include <cstdio>
#include <cmath>
#define pb push_back
using namespace std;
inline int read()
{
int sum=0,ff=1; char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-') ff=-1;
ch=getchar();
}
while(isdigit(ch))
sum=sum*10+(ch^48),ch=getchar();
return sum*ff;
}
const int N=3e5+5;
const double PI=acos(-1.0);
struct complex
{
double x,y;
complex (double xx=0,double yy=0) { x=xx; y=yy; }
};
complex a[N],b[N],c[N];
int lim=1,l,r[N],n;
inline complex operator + (complex a,complex b)
{
return complex(a.x+b.x,a.y+b.y);
}
inline complex operator - (complex a,complex b)
{
return complex(a.x-b.x,a.y-b.y);
}
inline complex operator * (complex a,complex b)
{
return complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);
}
inline void fft(complex *A,int inv)
{
for ( int i=0;i<lim;i++ )
if(i<r[i]) swap(A[i],A[r[i]]);
for ( int mid=1;mid<lim;mid<<=1 )
{
complex dwg(cos(PI/mid),inv*sin(PI/mid));
for ( int R=mid<<1,j=0;j<lim;j+=R )
{
complex w(1,0);
for ( int k=0;k<mid;k++,w=w*dwg )
{
complex x=A[j+k],y=w*A[j+mid+k];
A[j+k]=x+y;
A[j+mid+k]=x-y;
}
}
}
}
int main()
{
scanf("%d",&n);
for ( int i=1;i<=n;i++ )
{
double x;
scanf("%lf",&a[i].x);
b[n-i+1].x=a[i].x;
}
while(lim<=n*2) lim<<=1,l++;
for ( int i=1;i<=n;i++ ) c[i].x=(double)(1.0/(double)i/(double)i);
for ( int i=0;i<lim;i++ ) r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
fft(a,1);
fft(b,1);
fft(c,1);
for ( int i=0;i<lim;i++ )
{
a[i]=a[i]*c[i];
b[i]=b[i]*c[i];
}
fft(a,-1);
fft(b,-1);
for ( int i=0;i<lim;i++ )
{
a[i].x/=lim;
b[i].x/=lim;
}
for ( int i=1;i<=n;i++ )
printf("%.5lf\n",a[i].x-b[n-i+1].x);
return 0;
}
Sol
- 这道题目可以用
SA解决,当然
FFT也是一个很好的选择
- 我们首先要想到一个判断只改变
3个以内字符完成匹配的方法。因为只有四个字符
A,T,C,G。我们对于每一个字符做一下处理:比如对于
A来说,我们把
S0中除了
A以外的字符标记成
0,把
A标记为
1。那么对于每一个其实位置
k,
Sk..k+m−1与
T0..m−1的匹配数即为
∑i=0m−1Sk+i×Ti。
- 于是我们设
Am−i−1=Ti
- 那么原式变为
∑i=0m−1Sk+i×Am−i−1
- 又变为
∑i+j=m−k−1Si×Aj
- 这样子就成了卷积形式用
FFT即可。
Code
#include <iostream>
#include <cstdio>
#include <cmath>
#define pb push_back
using namespace std;
inline int read()
{
int sum=0,ff=1; char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-') ff=-1;
ch=getchar();
}
while(isdigit(ch))
sum=sum*10+(ch^48),ch=getchar();
return sum*ff;
}
const int N=5e5+5;
const double PI=acos(-1.0);
struct complex
{
double x,y;
complex (double xx=0,double yy=0) { x=xx; y=yy; }
};
complex a[N],b[N],c[N];
int lim=1,l,r[N],n,m,ans[N];
string S,T;
inline complex operator + (complex a,complex b)
{
return complex(a.x+b.x,a.y+b.y);
}
inline complex operator - (complex a,complex b)
{
return complex(a.x-b.x,a.y-b.y);
}
inline complex operator * (complex a,complex b)
{
return complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);
}
inline void fft(complex *A,int inv)
{
for ( int i=0;i<lim;i++ )
if(i<r[i]) swap(A[i],A[r[i]]);
for ( int mid=1;mid<lim;mid<<=1 )
{
complex dwg(cos(PI/mid),inv*sin(PI/mid));
for ( int R=mid<<1,j=0;j<lim;j+=R )
{
complex w(1,0);
for ( int k=0;k<mid;k++,w=w*dwg )
{
complex x=A[j+k],y=w*A[j+mid+k];
A[j+k]=x+y;
A[j+mid+k]=x-y;
}
}
}
}
int main()
{
int TT=read();
for (;TT--;)
{
cin>>S>>T;
n=S.length();
m=T.length();
for ( int kind=1;kind<=4;kind++ )
{
char alb;
if(kind==1) alb='A';
if(kind==2) alb='T';
if(kind==3) alb='C';
if(kind==4) alb='G';
for ( int i=0;i<n;i++ ) a[i].x=(alb==S[i]);
for ( int i=0;i<m;i++ ) b[m-i-1].x=(alb==T[i]);
lim=1,l=0;
while(lim<=n+m) lim<<=1,l++;
for ( int i=0;i<lim;i++ ) r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
fft(a,1);
fft(b,1);
for ( int i=0;i<lim;i++ ) c[i]=a[i]*b[i];
fft(c,-1);
for ( int i=m-1;i<=n+m-1;i++ )
ans[i-m+1]+=(int)(c[i].x/lim+0.5);
for ( int i=0;i<lim;i++ )
a[i]=b[i]=(complex){0,0};
}
int res=0;
for ( int i=0;i<n-m+1;i++ )
if(ans[i]+3>=m)
res++;
printf("%d\n",res);
for ( int i=0;i<=max(n,m);i++ ) ans[i]=0;
}
return 0;
}
Sol
- 首先我们考虑没有
∗的匹配即:
F(S,T)=∑i=0n−1(Si−Ti)2。有人可能会问,为什么不写成
F(S,T)=∑i=0n−1∣Si−Ti∣,虽然在意思上是相同的,但是绝对值写法不能加速了,而上一种可以转化
F(S,T)=∑i=0n−1(Si+Ti)2−2∗∑i=0n−1Si×Ti,而这后半部分又可以转化为卷积形式即
∑i=0n−1Si×Tn−i−1′,这可以快速
FFT计算。
- 然后我们考虑有
∗的匹配:因为
∗什么都可以匹配所以把他看作
0(因为
F(S,T)=∑i=0n−1(Si−Ti)2=0才算匹配成功)。那么我们考虑有哪几种情况
Si=Ti:
-
Si=Ti
-
Si=∗
-
Ti=∗
- 所以我们构造新的
F(S,T),即
F(S,T)=∑i=0n−1(Si−Ti)2×Si×Ti。这样子只要三种情况满足其一即可匹配(这还是比较好理解的)。但上式好象用处不大,我们尝试把他拆开:
- 这样就变为
F(S,T)=∑i=0n−1Si3Ti−2∑i=0n−1Si2Ti2−∑i=0n−1SiTi3
- 然后将其翻转:
F(S,T)=∑i=0n−1Si3Tn−i−1′−2∑i=0n−1Si2Tn−i−1′2∑i=0n−1SiTn−i−1′3
- 那么就简单了,分别对各项做
FFT即可,时间复杂度
O(6×nlogn)
Code
#include <iostream>
#include <cstdio>
#include <cmath>
#include <vector>
using namespace std;
inline int read()
{
int sum=0,ff=1; char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-') ff=-1;
ch=getchar();
}
while(isdigit(ch))
sum=sum*10+(ch^48),ch=getchar();
return sum*ff;
}
const int N=1e6+2e5+5;
const double PI=acos(-1.0);
struct complex
{
double x,y;
complex (double xx=0,double yy=0) { x=xx,y=yy; }
};
complex a[N],b[N],c[N],d[N],e[N],f[N];
int l,lim=1,r[N],s[N],t[N],n,m;
string S,T;
vector<int> ans;
inline complex operator + (complex a,complex b)
{
return complex(a.x+b.x,a.y+b.y);
}
inline complex operator - (complex a,complex b)
{
return complex(a.x-b.x,a.y-b.y);
}
inline complex operator * (complex a,complex b)
{
return complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);
}
inline void fft(complex *A,int inv)
{
for ( int i=0;i<lim;i++ )
if(i<r[i]) swap(A[i],A[r[i]]);
for ( int mid=1;mid<lim;mid<<=1 )
{
complex dwg(cos(PI/mid),inv*sin(PI/mid));
for ( int R=mid<<1,j=0;j<lim;j+=R )
{
complex w(1,0);
for ( int k=0;k<mid;k++,w=w*dwg )
{
complex x=A[j+k],y=w*A[j+mid+k];
A[j+k]=x+y;
A[j+mid+k]=x-y;
}
}
}
}
int main()
{
m=read();
n=read();
cin>>T;
cin>>S;
for ( int i=0;i<n;i++ ) s[i]=(S[i]=='*')?0:(S[i]-'a'+1);
for ( int i=0;i<m;i++ ) t[m-i-1]=(T[i]=='*')?0:(T[i]-'a'+1);
while(lim<=n+m) lim<<=1,l++;
for ( int i=0;i<lim;i++ ) r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
for ( int i=0;i<n;i++ )
{
a[i].x=s[i]*s[i]*s[i];
b[i].x=-2*s[i]*s[i];
c[i].x=s[i];
}
for ( int i=0;i<m;i++ )
{
d[i].x=t[i];
e[i].x=t[i]*t[i];
f[i].x=t[i]*t[i]*t[i];
}
fft(a,1); fft(b,1); fft(c,1);
fft(d,1); fft(e,1); fft(f,1);
for ( int i=0;i<lim;i++ )
a[i]=a[i]*d[i]+b[i]*e[i]+c[i]*f[i];
fft(a,-1);
for ( int i=0;i<n-m+1;i++ )
if((int)(a[i+m-1].x/lim+0.5)==0) ans.push_back(i+1);
printf("%d\n",ans.size());
for ( int i=0;i<ans.size();i++ )
printf("%d ",ans[i]);
return 0;
}