最近舟游疯狂出货,心情很好~
具体的推导见这篇:胡小兔 - 小学生都能看懂的FFT!!! (写的很好,不过本小学生第一次没看懂0.0)
总结下关键内容
~ Part 0 ~ 点值表示
对于一$n$项多项式$A(x)=a_0+a_1x+a_2x^2+...+a_{n-1}x^{n-1}$
我们代入$n$个不同的数$x_i$,得到$n$个值$y_i=A(x_i)$
则称这$n$个有序数对$(x_i,y_i)$为多项式$A(x)$的点值表示(可以认为是$xOy$平面上的点)
可以证明(略),通过这个$n$个点值可以还原出原多项式
~ Part 1 ~ 多项式表示转点值表示:
将原多项式$A(x)$的次数补成$2$的幂次,然后进行递归拆分
设当前一层的多项式为$B(x)$,项数为$n$,即:$B(x)=b_0+b_{1}x+b_{2}x^{2}+...+b_{n-1}x^{n-1}$
先将此多项式的按照奇偶项系数拆成两个式子
\[B_0(x)=b_0+b_2x+b_4x^2+...+b_{n-2}x^{\frac{n}{2}-1}\]
\[B_1(x)=b_1+b_3x+b_5x^2+...+b_{n-1}x^{\frac{n}{2}-1}\]
那么可以将$B(x)$重新表示:$B(x)=B_0(x^2)+xB_1(x^2)$
我们希望将复平面上单位圆的点代入,得到$B(\omega^{0}_{n})$,...,$B(\omega^{n-1}_{n})$的值
若$0\leq i<\frac{n}{2}$,则有【这里使用了复数运算律:因为$\omega$在单位圆上,所以$\omega^i_n\cdot\omega^j_n=\omega^{i+j}_n$;因为$n$为$2$的倍数,所以$\omega^{2i}_n=\omega^i_{\frac{n}{2}}$】
\begin{align*}B(\omega^i_n)&=B_0((\omega^i_n)^2)+\omega^i_nB_1((\omega^i_n)^2)\\&=B_0(\omega^{2i}_n)+\omega^i_nB_1(\omega^{2i}_n)\\&=B_0(\omega^i_{\frac{n}{2}})+\omega^i_nB_0(\omega^i_{\frac{n}{2}})\end{align*}
而对于单位圆上的另一半,则有【这里追加一个复数运算律:因为$n$为$2$的倍数,所以$\omega^{i+\frac{n}{2}}_n=-\omega^i_n$】
\begin{align*}B(\omega^{i+\frac{n}{2}}_n)&=B_0((\omega^{i+\frac{n}{2}}_n)^2)+\omega^{i+\frac{n}{2}}_nB_1((\omega^{i+\frac{n}{2}}_n)^2)\\&=B_0(\omega^{2i+n}_n)-\omega^i_nB_1(\omega^{2i+n}_n)\\&=B_0(\omega^i_{\frac{n}{2}})-\omega^i_nB_0(\omega^i_{\frac{n}{2}})\end{align*}
所以我们将这一层计算$B(\omega^0_n)$,...,$B(\omega^{n-1}_{n-1})$的问题变为计算下一层$B_0(\omega^0_{\frac{n}{2}})$,...,$B_0(\omega^{\frac{n}{2}-1}_{\frac{n}{2}})$,$B_1(\omega^0_{\frac{n}{2}})$,...,$B_1(\omega^{\frac{n}{2}-1}_{\frac{n}{2}})$
这样,总的复杂度是$O(N logN)$
~ Part 2 ~ 点值转多项式系数
设原多项式为$A(x)=a_0+a_1x+a_2x^2+...+a_{n-1}x^{n-1}$,其中补成$2$的幂次项后项数为$n$
则分别代入$\omega^0_n$,$\omega^1_n$,...,$\omega^{n-1}_n$,得到$y_i=A(\omega^i_n)$,$i=0,1,...,n-1$,即上面得到的$n$个点值
令$B(x)=y_0+y_1x+y_2x^2+...+y_{n-1}x^{n-1}$
再分别代入$\omega^0_n$,$\omega^{-1}_n$,...,$\omega^{-(n-1)}_n$,运算(这里略去)得到$a_i=\frac{B(\omega^{-i}_n)}{n}$,$i=0,1,...,n-1$
按这个思路写出的代码如下(实现了多项式系数转点值,再转回多项式系数)
大佬用的数组指针确实方便
#include <cstdio> #include <complex> #include <cmath> #include <cstring> #include <algorithm> using namespace std; typedef complex<double> cp; const int N=100005; const double pi=acos(-1.0); inline cp omega(int x,int n,int rev) { if(rev) x=-x; return cp(cos(x*2*pi/(double)n),sin(x*2*pi/(double)n)); } cp tmp[N]; void fft(cp *a,int n,int rev) { if(n==1) return; for(int i=0;i<n;i++) tmp[i]=a[i]; for(int i=0;i<n/2;i++) { a[i]=tmp[i*2]; a[n/2+i]=tmp[i*2+1]; } fft(a,n/2,rev); fft(a+n/2,n/2,rev); for(int i=0;i<n/2;i++) { cp x=omega(i,n,rev); tmp[i]=a[i]+x*a[n/2+i]; tmp[n/2+i]=a[i]-x*a[n/2+i]; } for(int i=0;i<n;i++) a[i]=tmp[i]; } int n; cp a[N]; int main() { // freopen("input.txt","r",stdin); scanf("%d",&n); for(int i=0;i<n;i++) { int x; scanf("%d",&x); a[i]=cp((double)x,0); } int sz=1; while(sz<n) sz<<=1; n=sz; fft(a,n,0); fft(a,n,1); for(int i=0;i<n;i++) printf("%.2lf ",a[i].real()/(double)n); printf("\n"); return 0; }