1. 程式人生 > >[學習筆記] FFT

[學習筆記] FFT

填一下$FFT$ 的坑。 我們的目標是求$A\times B$的係數($A,B$為係數已知的多項式) ## 關於 $\omega$ 大概講一下 我們有複數 $\omega$。 $\omega_n^k=\cos \frac{2k\pi}{n}+i\times\sin\frac{2k\pi}{n}$ 可以把它看作把單位圓進行n等分,然後進行標號,$k$ 每增加1,即使逆時針往後移一個 這東西有幾個性質: - $\omega_n^{k+n}=\omega_n^k$ 相當於轉了一圈又回來了 - $\omega_n^{k+\frac n 2}=-\omega_n^k$ 轉到對面去了 - $\omega_n^k=\omega_{\frac n 2}^{\frac k 2}$ 進行了縮放,但還是在同一個位置 ## 大體思路 對於一個n-1次的多項式$A(x)$,我們如果知道它的$n$個不同的$x$對應的值(我們稱它為點值),那麼我們就能還原它的係數。 - 如果我們要求 $A(x)\times B(x)$ 的係數,那麼我們就可以先算出 $A$ 和 $B$ 在$n$個$x$處對應的點值,將兩者相乘,這即是$A\times B$在$x$處的點值,然後將它還原即可。 而$FFT$就用到了這一點。 總體思路就是: - 我們首先將 $\omega_n^0,\omega_n^1,\omega_n^2,...,\omega_n^{n-1}$ 代入兩多項式,算出各多項式的點值(我們稱其為 $DFT$ 操作),再將兩者相乘,將得到的值還原為係數(我們稱其為 $iDFT$ 操作)。 ## $DFT$ $\begin{aligned}A(\omega_n^k)&=\sum_{i=0}^{n-1}a_0\times\omega_n^{ik}\\&=\sum_{i=0}^{\lfloor\frac n 2\rfloor}a_{2i}\times(\omega_n^2)^{ik}+\omega_n^1\sum_{i=0}^{\lfloor\frac n 2\rfloor}a_{2i+1}\times(\omega_n^2)^{ik}\end{aligned}$ - 進行奇偶分類: $A_0(\omega_n^{k})=\sum_{i=0}^{\lfloor\frac n 2\rfloor}a_{2i}\times(\omega_n^{k})^i,A_1(\omega_n^{k})=\sum_{i=0}^{\lfloor\frac n 2\rfloor}a_{2i+1}\times(\omega_n^{k})^i$ - 繼續把式子推下去: ($k\le\frac n 2$) $\begin{aligned}A(\omega_n^k)&=A_0(\omega_n^{k})^2+\omega_n^kA_1(\omega_n^{k})^2\\&=A_0(\omega_{\frac n 2}^{k})+\omega_n^kA_1(\omega_{\frac n 2}^{k})\end{aligned}$ $\begin{aligned}A(\omega_n^{k+\frac n 2})&=A_0(\omega_n^{k+\frac n 2})^2+\omega_n^{k}A_1(\omega_n^{k+\frac n 2})^2\\&=A_0(\omega_{\frac n 2}^{k+\frac n 2})+\omega_n^{k}A_1(\omega_{\frac n 2}^{k+\frac n 2})\\&=A_0(\omega_{\frac n 2}^k)-\omega_n^{k}A_1(\omega_{\frac n 2}^k)\end{aligned}$ 有了這個式子我們就能迭代求解了。 時間複雜度是$\varTheta(n\log n)$ 的。 ## $iDFT$ 進入$iDFT$ 的過程。 設 $C=A\times B$ 考慮: $\begin{aligned}C(\omega_n^k)&=DFT.A(\omega_n^k)\times DFT.B(\omega_n^k)\\\sum_{i=0}^{n-1}c[i]\times(\omega_n^k)^i&=DFT.A(\omega_n^k)\times DFT.B(\omega_n^k)\\\sum_{i=0}^{n-1}DFT.A(\omega_n^i)\times DFT.B(\omega_n^i)\times(\omega_n^{-k})^i&=\sum_{i=0}^{n-1}\sum_{j=0}^{n-1}c[j]\times(\omega_n^i)^j\times(\omega_n^{-k})^i\\\sum_{i=0}^{n-1}DFT.A(\omega_n^i)\times DFT.B(\omega_n^i)\times(\omega_n^{-k})^i&=\sum_{j=0}^{n-1}c[j]\sum_{i=0}^{n-1}\omega_n^{i(j-k)}\end{aligned}$ - 當 $j=k$ 時: $\omega_n^{i(j-k)}=\omega_n^0=1$ $\sum_{i=0}^{n-1}\omega_n^{i(j-k)}=n$ - 當 $j\ne k$ 時: $\begin{aligned}\sum_{i=0}^{n-1}\omega_n^{i(j-k)}&=\sum_{i=0}^{\lfloor\frac{n-1} 2\rfloor}\omega_n^{i(j-k)}+\omega_n^{(n-i)(j-k)}\\&=\sum_{i=0}^{\lfloor\frac{n-1}2\rfloor}\omega_n^{i(j-k)}+\omega_n^{-i(j-k)}\\&=0\end{aligned}$ - 那麼我們就有: $\begin{aligned}DFT.A(\omega_n^i)\times DFT.B(\omega_n^i)\times(\omega_n^{-k})^i&=c[k]\times n\\c[k]&=\frac{DFT.A(\omega_n^i)\times DFT.B(\omega_n^i)\times(\omega_n^{-k})^i}n\end{aligned}$ - 所以我們只需要把$DFT.A(\omega_n^i)\times DFT.B(\omega_n^i)$ 當作係數,將$\omega_n^{-k}$代入$DFT$的過程,求出來的點值除以$n$,就是$A\times B$的係數。 ## 程式碼: ```cpp #include #define cp complex using namespace std; const int N=2100005; double pi=acos(-1); cp a[N],b[N],c[N]; int n,m,lg; void FFT(cp *a,int n,int inv){ if (n==1) return; int m=n/2; cp a0[n],a1[n]; for (int i=0;i #define cp complex using namespace std; const int N=2100005; double pi=acos(-1); cp a[N],b[N],c[N],u,t; int n,m,x,lg; void FFT(cp *a,int n,int opt){ for (int i=0;i>j)&1) t|=(1<<(lg-j-1)); if (i
>15\\&b[i]=A[i]\&(2^{15}-1)\\&c[i]=B[i]>>15\\&d[i]=B[i]\&(2^{15}-1)\end{aligned}$ - 分別求出他們的點值(要取模) - 將次數相同的項組合: $Ans_1=D.a\times D.c$,$Ans_2=D.b\times D.c+D.a\times D.d$,$Ans_3=D.b\times D.d$ ($DFT.x$被簡寫為$D.x$) - 對$Ans_1,Ans_2,Ans_3$進行$iDFT$ - 還原出係數 $\begin{aligned}Ans[x]=iDFT.Ans_1[x]\times 2^{30}+iDFT.Ans_2\times2^{15}+iDFT.Ans_3\end{aligned}$ (要取模) 這裡沒啥好講的,就來簡單講一下為什麼拆完之後分別做$DFT$、$iDFT$,再分別取模還是對的吧(挺顯然的,其實) 我們把原係數拆分之後再做$DFT$,在$DFT$,重新組和,$iDFT$的過程中,是不會爆$long$ $long$ 的,也就是說我們求出的就是未經過取模的真實點值。 有了點值,再進行$iDFT$,我們還原出的係數也就是將原係數進行拆分後的係數。 然後按照原來的比例相乘組合即可,在此時取模是沒有影響的,得到也即是真正的係數取模後的值。 - 程式碼: ```cpp #include
#define ll long long using namespace std; const int N=1<<19|5; long double pi=acos(-1); struct cp{ long double a,b; cp() {a=b=0;} cp(long double _a, long double _b) {a=_a,b=_b;} cp operator +(cp x) const {return cp(a+x.a,b+x.b);} cp operator -(cp x) const {return cp(a-x.a,b-x.b);} cp operator *(cp x) const {return cp(a*x.a-b*x.b,a*x.b+b*x.a);} cp Conj() {return cp(a,-b);} }; cp p[N],q[N],a[N],b[N],c[N],d[N],kx,bx,ky,by,u,t; int ret[N],L1,L2; ll x[N],y[N],ans[N],mod,len,lg; void FFT(cp *a,int n,long double opt){ int m=1; for (int i=0;i>1]>>1)|((i&1)<<(lg-1)); for (int i=0;i<=L1;i++) p[i]=cp(x[i]>>15,x[i]&32767); for (int i=0;i<=L2;i++) q[i]=cp(y[i]>>15,y[i]&32767); FFT(p,n,1); FFT(q,n,1); for (int i=0;i