[日志]9-25

我不是柳橙汁

2019-09-25 11:33:04

Personal

## 回来第一次复习FFT 今天就写一下FFT的小结 在此安利我所复习的博客,stO [自为风月马前卒的FFT详解](https://www.cnblogs.com/zwfymqz/p/8244902.html) ### 前置知识 1.复数相关 复数的表示法 $a+bi$ 复数加减乘 $$(a+bi)+(c+di)=(a+c)+(b+d)i$$ $$(a+bi)-(c+di)=(a-c)+(b-d)i$$ $$(a+bi)*(c+di)=(ac-bd)+(ad+bc)i$$ 复数可在直角坐标系中表示 2.点值表示法 如一个多项式 他的系数表示法为 $$A_n=a_0+a_1x+a_2x^2+...+a_nx^n$$ 假定这么一个多项式 $$A_n=2+5x+3x^2$$ 将若干个$x$代入多项式中,得到一系列点$(x_i,y_i)$ 比如这个多项式,我们将$x=0,x=1,x=2$代入 得到$(0,2),(1,10),(2,24)$这一系列点 我们也可以用这一系列点来表示这个多项式 为什么要用到这个呢 在正常的多项式乘法中 若是两个多项式相乘 需要将两个多项式中每个项都乘一遍,复杂度$o(n^2)$ 若是采用点值表示法,只需要将每两个对应的点相乘,复杂度$o(n)$ 3.1.**单位根** 以下内容默认n为2的整数次幂 在复数平面上,单位圆就是以原点为圆心,半径为1的一个圆 将这个圆n等分,按照分割线作n个向量 设幅度为正最小的那个向量为$\omega_n$ 我们称之为n次单位根 可以知道其他n-1个向量分别为$\omega_n^2,\omega_n^3,...,\omega_n^n$ 其中$\omega_n^n=\omega_n^0=1$ 这些单位根的值可以通过欧拉公式计算 $$\omega_n^k=\cos \frac{2k\pi}{n}+i\sin \frac{2k\pi}{n}$$ 在代数中,若$z^k=1$,我们称$z$为$k$次单位根 3.2.单位根的性质 $$\omega_n^k=\cos \frac{2k\pi}{n}+i\sin \frac{2k\pi}{n}$$ $$\omega_{2n}^{2k}=\omega_n^k$$ $$\omega_n^{k+\frac{n}{2}}=-\omega_n^k$$ $$\omega_n^n=\omega_n^0=1$$ $k\not= 0$时 $$\sum_{i=1}^n\omega_n^{ki}=0$$ $k=0$时 $$\sum_{i=1}^n\omega_n^{ki}=n$$ ### 快速傅里叶变换(FFT)(Fast Fourier Transfromation) 我们设一个多项式 $$A(x)=a_0+a_1x+a_2x^2+...+a_{n-1}^{n-1}$$ 将其按照奇偶拆开 $$=(a_0+a_2x^2+....+a_{n-2}x^{n-2})+(a_1x+a_3x^3+...+a_{n-1}x^{n-1})$$ 奇数项提出一个x $$=(a_0+a_2x^2+....+a_{n-2}x^{n-2})$$ $$+x(a_1+a_3x^2+...+a_{n-1}x^{n-2})$$ 令 $$A_1(x)=a_0+a_2x+....+a_{n-2}x^{\frac{n}{2}-1}$$ $$A_2(x)=a_1+a_3x+...+a_{n-1}x^{\frac{n}{2}-1}$$ 可知 $$A(x)=A_1(x^2)+xA_2(x^2)$$ 将$\omega_n^k$代入 $$A(\omega_n^k)=A_1(\omega_n^{2k})+\omega_n^kA_2(\omega_n^{2k})$$ 再将$\omega_n^{k+\frac{n}{2}}$代入 $$A(\omega_n^{k+\frac{n}{2}})=A_1(\omega_n^{2k+n})+\omega_n^{k+\frac{n}{2}}A_2(\omega_n^{2k+n})$$ $$=A_1(\omega_n^{2k})-\omega_n^kA_2(\omega_n^{2k})$$ 我们将两个式子比较 $$A(\omega_n^k)=A_1(\omega_n^{2k})+\omega_n^kA_2(\omega_n^{2k})$$ $$A(\omega_n^{k+\frac{n}{2}})=A_1(\omega_n^{2k})-\omega_n^kA_2(\omega_n^{2k})$$ 发现枚举其中一个时,可以在$o(1)$的时间将另一个枚举出来 每次我们可以将问题缩小一半 这样就类似线段树的时间复杂度$o(nlogn)$ 本身需要$o(n^2)$的时间,我们拿$o(n+2nlogn)$就完成了,得到了优化 ### 快速傅里叶逆变换(IFFT)(Fast Fourier Inverse Transformation) 但是,我们常用的表示法并不是点值表示法 我们还应该将点值表示法转化为系数表示法 这个过程叫做**傅里叶逆变换** 首先 我们设$(y_1,y_2,...,y_n)$为$(a_1,a_2,...,a_n)$的点值表示 设另一个向量$(c_1,c_2,...,c_n)$满足 $$c_k=\sum_{i=0}^{n-1}y_i(\omega_n^{-k})^i$$ $$=\sum_{i=0}^{n-1}(\sum_{j=0}^{n-1}a_j(\omega_n^{i})^j)(\omega_n^{-k})^i$$ $$=\sum_{i=0}^{n-1}(\sum_{j=0}^{n-1}a_j(\omega_n^{i})^j)(\omega_n^{-k})^i$$ $$=\sum_{j=0}^{n-1}a_j\sum_{i=0}^{n-1}(\omega_n^i)^{(j-k)}$$ 于是我们知道 $j=k$时 $$\sum_{i=0}^{n-1}(\omega_n^i)^{(j-k)}=n$$ 而$j\not=k$时 $$\sum_{i=0}^{n-1}(\omega_n^i)^{(j-k)}=0$$ 于是 $$c_k=na_k$$ 即 $$a_k=\frac{c_k}{n}$$ 所以我们对乘起来的点值表示再做一次FFT 然后除去n就是所得到的多项式了 所用时间复杂度为$o(2nlogn+n+nlogn)$,即$o(nlogn)$ ### 迭代实现 我们也可以尝试用递归的方法来完成FFT,但这样并不是最优的办法 如果我们事先就将需要转换的位置弄明白,这样可以直接转换 可以发现 反转后的序列的二进制数也反转过来 于是我们可以预处理我们需要得到的数列 就可以在$o(n)$的时间复杂度内完成了 ### 板子题 [【模板】多项式乘法](https://www.luogu.org/problem/P3803) ```cpp #include<cstdio> #include<cstdlib> #include<cmath> #include<iostream> using namespace std; #define fr(i,a,b) for(register long long i=a;i<=b;i++) const double Pi=acos(-1.0); struct complex{ double x,y; complex(double xx=0,double yy=0){x=xx,y=yy;} }a[5000010],b[5000010]; complex operator + (complex a,complex b){return complex(a.x+b.x,a.y+b.y);} complex operator - (complex a,complex b){return complex(a.x-b.x,a.y-b.y);} complex operator * (complex a,complex b){return complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);} long long n,m,limit,l; long long r[5000010]; inline long long v_in(){ char ch=getchar();long long sum=0,f=1; while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();} while(isdigit(ch)) sum=(sum<<3)+(sum<<1)+(ch^48),ch=getchar(); return sum*f; } void FFT(complex *A,long long type){ fr(i,0,limit-1) if(i<r[i]) swap(A[i],A[r[i]]); for(register long long mid=1;mid<limit;mid<<=1){ complex Wn(cos(Pi/mid),type*sin(Pi/mid)); for(register long long j=0;j<limit;j+=(mid<<1)){ complex W(1,0); for(register long long k=0;k<mid;k++,W=W*Wn){ complex x=A[j+k],y=W*A[j+mid+k]; A[j+k]=x+y; A[j+mid+k]=x-y; } } } } void inpt(){ n=v_in(),m=v_in(); fr(i,0,n) a[i].x=v_in(); fr(i,0,m) b[i].x=v_in(); limit=1,l=0; while(limit<n+m+1) limit<<=1,l++; fr(i,0,limit-1) r[i]=(r[i>>1]>>1)|((i&1)<<(l-1)); } void work(){ FFT(a,1); FFT(b,1); fr(i,0,limit) a[i]=a[i]*b[i]; FFT(a,-1); } int main(){ inpt(); work(); fr(i,0,n+m) printf("%lld ",(int)(a[i].x/limit+0.5)); printf("\n"); return 0; } ```