前置知识:
1.多项式
定义
形如:
多项式表示法:
系数表示法
就是上式的写法
点值表示法
在f(x)上取n个点,就能唯一确定的表示出这个多项式。
证明如下:
n点集合c
定义集合A={
}为某一个的n点的集合c所表示的多项式的系数集合。
反证:
若不然,则
集合B,B
A,并且B也是c所表示多项式的系数集合。
此时,令Ci=Ai-Bi,那么带入c中的每一个元素,C所代表的多项式的值都会是0,也就是说,该n-1次多项式有n个零点,这显然与事实不符,所以证毕。
多项式相乘
如果用系数表示法直接相乘,那么计算的复杂度是O(
)的。
但是如果用点值表示法相乘,由于函数的相乘是每一个点的两个函数值相乘,所以复杂度是O(n)的。
2.复数相关
1.复数定义
在数学上,我们定义i,满足
也就是说
定义一个复数为
那么由此我们就把数的定义扩充到了更大的范围。
2.复平面
复数的定义 ,我们可以把a看成x轴,b看成y轴,那么每一个复数都可以看做在复平面上的一个点。当然,原本的实数就是复平面中x轴上的点。
3.复平面上的运算法则
复数的运算法则:
复平面上的复数可以看做向量,那么复平面上的复数可以表示为(辐角(与x轴的夹角大小),长度)。对于这种表示方法,向量的相加的法则是:长度相乘,辐角相加。
证明:
假设两个复数
那么两个复数的长度分别为
相乘等于
而两个向量相乘后得到的向量
的长度也等于上式的值。
辐角相加也类似可证,只不过需要用到定理
,这里就省略了。
单位根
定义
定义
为在复平面上,以1为半径作圆,把圆等分成n份。
那么
就表示这个n等分圆中,从x轴开始,逆时针数的第k条等分线所表示的复数。
我们规定x轴上的等分线为第0条,同时也是的n条,也就是说
,意思就是等分从x轴开始。
那么有一个显然的计算
的方法:
性质
性质1
这个可以感性理解一下,一个圆分成n份,取第k份,和一个圆分成2n份,取第2k份,实际上是相同的。
性质2
这个性质也很好理解,画一个图即可。
FFT
FFT是实现傅里叶变换(DFT)和离散傅里叶变换(IDFT)的方法
1.离散傅里叶变换(DFT)
DFT实际上是将多项式由系数表示法变为点值表示法的过程。
假设有一个n次的多项式
如果用暴力的方法实现这一过程,那么就是随意取n个点,分别带入多项式,求出此时的值,这样做的复杂度是O(
)的。
那么我们考虑特殊的取点,
,带入这些点,会对我们的转变效率有什么提高呢?
我们考虑一个多项式F
我们把它分成偶次
和奇次
两个部分
那么
假设
当我们带入
的时候,用一下[性质2],式子是:
利用[性质1],变形一下:
此时我们再带入
,得到:
利用[性质2],变形一下,得:
这两个式子有什么用呢?
实际上,我们在这里证明了每一个
都可以由某两个
得到,所以如果知道了所有
的值,我们可以在O(n)时间内求出所有
的值。那么对于这个问题我们可以分而治之,也就是用分治递归解决。
离散傅里叶逆变换(IDFT)
我们如果要为这个过程再设计一种方法的话,那未免有点麻烦,我们可以尝试通过某张变换方式,是的IDFT也可以套用DFT的解决方法,这样就会方便很多。
首先考虑DFT的过程,可一转变为一个矩阵,如下:
我们假设最左边的系数矩阵为A,那么我们构造一个矩阵B,使得
,那么B矩阵就是这个样子:
我们让A与B相乘,假设结果为C,那么可以写出:
带入A和B的值,得到:
由此我们可以得出:
i=j时:
否则:
那么C矩阵实际上是单位矩阵的n倍!
那么我们就可以这么做:
然而这就可以直接再做一遍FFT来解决。
代码实现
模板题:UOJ 34
#include<bits/stdc++.h>
using namespace std;
const int maxn=100005;
int n,m,bitn;
const double Pi=acos(-1.0);
struct comx{
double x,y;
comx (double x1=0,double y1=0){x=x1,y=y1;}
comx operator +(comx b){return comx(x+b.x,y+b.y);}
comx operator -(comx b){return comx(x-b.x,y-b.y);}
comx operator *(comx b){return comx(x*b.x-y*b.y,x*b.y+y*b.x);}
}a[maxn],b[maxn];
void FFT(int lim,comx *a,int p){
if (lim==1) return;
comx a1[lim>>1],a2[lim>>1];
for (int i=0;i<=lim;i+=2) a1[i>>1]=a[i],a2[i>>1]=a[i+1];
FFT(lim>>1,a1,p); FFT(lim>>1,a2,p);
comx omn=comx(cos(2.0*Pi/lim),1.0*p*sin(2.0*Pi/lim)),now=comx(1.0,0.0);
for (int i=0;i<(lim>>1);i++){
a[i]=a1[i]+now*a2[i];
a[i+(lim>>1)]=a1[i]-now*a2[i];
now=now*omn;
}
}
int main(){
scanf("%d%d",&n,&m);
for (int i=0;i<=n;i++) scanf("%lf",&a[i].x);
for (int i=0;i<=m;i++) scanf("%lf",&b[i].x);
bitn=1; while (bitn<=(n+m)) bitn<<=1;
FFT(bitn,a,1); FFT(bitn,b,1);
for (int i=0;i<=bitn;i++) a[i]=a[i]*b[i];
FFT(bitn,a,-1);
for (int i=0;i<=n+m;i++) printf("%.0lf ",a[i].x/(1.0*bitn));
printf("\n");
return 0;
}
迭代版FFT(蝴蝶操作)
#include<bits/stdc++.h>
using namespace std;
const int maxn=(1<<20)+5;
const double Pi=acos(-1.0);
int n,m,limn,R[maxn];
struct C{
double x,y;
C(double xx=0,double yy=0){x=xx,y=yy;}
C operator +(C b) {return C(x+b.x,y+b.y);}
C operator -(C b) {return C(x-b.x,y-b.y);}
C operator *(C b) {return C(x*b.x-y*b.y,x*b.y+b.x*y);}
}a[maxn],b[maxn],c[maxn],w[maxn];
void FFT(C a[],int lim){
for (int i=0;i<lim;i++) if (R[i]>i) swap(a[R[i]],a[i]);
for (int t=lim>>1,d=1;d<lim;d<<=1,t>>=1)
for (int i=0;i<lim;i+=(d<<1))
for (int j=0;j<d;j++){
C p=w[t*j]*a[i+j+d];
a[i+j+d]=a[i+j]-p,a[i+j]=a[i+j]+p;
}
}
int main(){
scanf("%d%d",&n,&m);
for (int i=0;i<=n;i++) scanf("%lf",&a[i].x);
for (int i=0;i<=m;i++) scanf("%lf",&b[i].x);
int L; for (limn=1,L=0;limn<=n+m;limn<<=1,L++);
for (int i=0;i<limn;i++)
R[i]=(R[i>>1]>>1)|((i&1)<<(L-1)),w[i]=C(cos(2.0*Pi*i/limn),sin(2.0*Pi*i/limn));
FFT(a,limn); FFT(b,limn);
for (int i=0;i<limn;i++)
c[i]=a[i]*b[i],w[i].y=-w[i].y;
FFT(c,limn);
for (int i=0;i<=n+m;i++) printf("%d ",(int)(c[i].x/limn+0.5));
printf("\n");
return 0;
}
一些练习:
hdu1402
#include<bits/stdc++.h>
using namespace std;
const int maxn=(1<<17)+5;
const double Pi=acos(-1.0);
int n,m,limn,R[maxn],ans[maxn];
char s[maxn];
struct comx{
double x,y;
comx(double xx=0,double yy=0){x=xx,y=yy;}
comx operator +(const comx b){return comx(x+b.x,y+b.y);}
comx operator -(const comx b){return comx(x-b.x,y-b.y);}
comx operator *(const comx b){return comx(x*b.x-y*b.y,x*b.y+b.x*y);}
}a[maxn],b[maxn],w[maxn];
void exc(comx *a,int len){for (int i=0;i<=len;i++) a[len-i].x=s[i]-'0';}
void fft(comx *a,int lim){
for (int i=0;i<lim;i++) if (R[i]>i) swap(a[R[i]],a[i]);
for (int t=lim>>1,d=1;d<lim;d<<=1,t>>=1)
for (int i=0;i<lim;i+=(d<<1))
for (int j=0;j<d;j++){
comx p=w[t*j]*a[i+j+d];
a[i+j+d]=a[i+j]-p,a[i+j]=a[i+j]+p;
}
}
int main(){
while (~scanf("%s",s)){
for (int i=0;i<=(1<<17);i++) a[i].x=a[i].y=0.0,b[i].x=b[i].y=0.0,R[i]=0,w[i].x=w[i].y=0.0;
n=strlen(s); exc(a,--n);
scanf("%s",s);
m=strlen(s); exc(b,--m);
int l=0; limn=1;
while (limn<=n+m) limn<<=1,l++;
for (int i=0;i<limn;i++){
R[i]=((R[i>>1]>>1)|((i&1)<<(l-1)));
w[i]=comx(cos(2.0*Pi/limn*i),sin(2.0*Pi/limn*i));
}
fft(a,limn); fft(b,limn);
for (int i=0;i<limn;i++) a[i]=a[i]*b[i],w[i].y=-w[i].y;
fft(a,limn);
memset(ans,0,sizeof(ans));
for (int i=0;i<limn;i++) ans[i]=(int)(a[i].x/limn+0.5);
for (int i=0;i<=n+m+1;i++) ans[i+1]+=ans[i]/10,ans[i]%=10;
int maxx=0;
for (int i=0;i<=n+m+1;i++) if (ans[i]>0) maxx=i;
for (int i=maxx;i>=0;i--) printf("%d",ans[i]); printf("\n");
}
return 0;
}
Bzoj 4503
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=(1<<18)+5;
const double Pi=acos(-1.0);
int n,m,limn,R[maxn];
char S[maxn],T[maxn];
struct comx{
double x,y;
comx(double xx=0,double yy=0){x=xx,y=yy;}
comx operator +(const comx b){return comx(x+b.x,y+b.y);}
comx operator -(const comx b){return comx(x-b.x,y-b.y);}
comx operator *(const comx b){return comx(x*b.x-y*b.y,x*b.y+y*b.x);}
}a[maxn],b[maxn],c[maxn],d[maxn],w[maxn];
double s;
void pre(){
int L=0; limn=1; while (limn<=n+m) limn<<=1,L++;
for (int i=0;i<limn;i++){
R[i]=((R[i>>1]>>1)|((i&1)<<(L-1)));
w[i]=comx(cos(2*Pi/limn*i),sin(2*Pi/limn*i));
}
}
void FFT(comx *a,int lim){
for (int i=0;i<lim;i++) if (R[i]>i) swap(a[R[i]],a[i]);
for (int t=lim>>1,d=1;d<lim;d<<=1,t>>=1)
for (int i=0;i<lim;i+=(d<<1))
for (int j=0;j<d;j++){
comx p=w[t*j]*a[i+j+d];
a[i+j+d]=a[i+j]-p,a[i+j]=a[i+j]+p;
}
}
void doit(comx *p,comx *q){
FFT(p,limn); FFT(q,limn);
for (int i=0;i<limn;i++) p[i]=p[i]*q[i],w[i].y=-w[i].y;
FFT(p,limn);
for (int i=0;i<limn;i++) w[i].y=-w[i].y,p[i].x/=limn;
}
ll cal(double x){return (ll)(x+0.5);}
int main(){
scanf("%s",S); scanf("%s",T);
n=strlen(S)-1; m=strlen(T)-1;
for (int i=0;i<=n;i++) c[i].x=S[i]-'a'+1;
for (int i=0;i<=m;i++)
if (T[i]!='?') b[m-i].x=T[i]-'a'+1; else b[m-i].x=0;
for (int i=0;i<=n;i++) a[i].x=c[i].x*c[i].x;
for (int i=0;i<=m;i++) d[i].x=b[i].x*b[i].x,s+=b[i].x*b[i].x*b[i].x;
pre(); doit(a,b); doit(c,d);
int ans=0;
for (int i=m;i<=n;i++)
if (cal(a[i].x)-2*cal(c[i].x)+cal(s)==0) ans++;
printf("%d\n",ans);
for (int i=m;i<=n;i++)
if (cal(a[i].x)-2*cal(c[i].x)+cal(s)==0) printf("%d\n",i-m);
return 0;
}