FFT 從入門到入土

2022-06-16 12:42:10 字數 3306 閱讀 7049

fft 是一種可以在 \(o(n \log n)\) 的時間複雜度內求解兩個多項式的乘積。實際上, fft 只是在求解方法上優化了 dft(離散傅利葉變換)的過程,並沒有提出新的理論。但是其高效的複雜度使得它被廣泛使用。

就是使用乙個多項式的係數序列來表達這個多項式。

\[f(x) = \sum_^ a_ix^i \leftrightarrow f(x) = \\}

\]乙個 \(n\) 次多項式可以由 \(n+1\) 個點來確定。

設 \(y_i = f(x_i)\) 則

\[f(x) = \sum_^ a_ix^i \leftrightarrow f(x) = \

\]定義多項式 \(a(x) = \sum_ ^ a_ix^i\),\(b(x)=\sum_^b_ix^i\),它們的乘積為

\[(a * b)(x) = \sum_^ x^i \sum_^ia_kb_

\]實際上就是簡單的直接相乘

\(n\) 次單位為滿足 \(\omega^n = 1\) 的解 \(\omega\),記作 \(\omega_n\)。

顯然,這 \(n\) 個單位根均勻分布在復平面的單位圓上。

自然地,\(w_n^k\) 的輻角為 \(\dfrac\)。因此,由尤拉公式,有

\[\omega_n^k = \cos\dfrac + i\cdot\sin \dfrac

\]\(\omega_n^k = \omega_^\)

等分成 \(pn\) 塊相當於先分成 \(n\) 塊後再將每個塊分成 \(p\) 塊,邊界自然不變,

\(\omega_n^} = -\omega_n^k\)。

指數加 \(\frac\) 相當於繞原點旋轉 \(\pi\) 。

\(\omega_n^p \cdot \omega_n^q = \omega_n^\)

設 \(\alpha = \frac, \beta = \frac\),則

\[\begin

\omega_^ \times \omega_^&=(\cos \alpha \cos \beta-\sin \alpha \sin \beta)+i(\sin \alpha \cos \beta+\cos \alpha \sin \beta)\\ &=\cos (\alpha+\beta)+i \sin

(\alpha+\beta)\\&=\omega_^

\end

\]我們發現,當兩個多項式 \(a,b\) 同時取 \(x\) 時,得到的點分別為 \(y_a,y_b\),\(a*b\) 取到的點即為 \((x,y_a \cdot y_b)\)。

這樣做是 \(o(n)\) 的。

對於任意係數多項式轉點值,我們可以隨意取 \(x\) 的值。

但如果我們代入 \(\omega\),就可以免去很多次方計算。

但即使是這樣,我們代入還是 \(o(n^2)\) 的複雜度。

fft 演算法的基本思想是分治

具體來說,我們從以下兩個方面進行拆分:

前 \(\frac\) 項和後 \(\frac\) 項。

奇數項和偶數項。

這樣拆分利用了單位根的性質:

將多項式拆分成兩半,則兩邊帶入的未知數的冪分別是 \(\omega_n^k\) 和 \(\omega_n^}\)。由性質二可以同時求出兩個位置上的值。

對於乙個多項式 \(f(x) = \sum c_ix^i\),我們可以將其拆成 \(a(x) = \sum c_x^\) 和 \(b(x)=\sum c_ x^\)。所以 \(f(x)=a(x^2)+xb(x^2)\)。我們發現 \(a,b\) 的每一項的指數都只差一,所以我們在求出偶數項的時候,也能求出奇數項。

證明:\[\begin

\text(f(\omega_n^k)) &=\text(g((\omega_n^k)^2)) + \omega_n^k \times \text(h((\omega_n^k)^2))\\ &=\text(g(\omega_n^)) + \omega_n^k \times \text(h(\omega_n^))\\ &=\text(g(\omega_^k)) + \omega_n^k \times \text(h(\omega_^k))

\end

\]由性質二可得

\[\text(f(\omega_n^))=\text(g(\omega_^k))-\omega_n^k\times \text(h(\omega_^k))

\]注意到這裡我們每次都要對每個係數進行點乘,所以必須將多項式長度拓展到二的整數冪上。

由遞迴式 \(t(n)=2t(\frac)+n\) 得到 \(t(n)=o(n\log n)\)。

將點值表示式轉回係數表示式的過程被稱作傅利葉逆變換。

考慮 \(\text\) 本質上是乙個線性變換,我們可以給得到的新矩陣乘上乙個逆矩陣,來得到初始矩陣。

由於這個變換矩陣內的元素均為單位復根,其逆矩陣也很好求,只要將每一項取倒數再除以 \(n\) 就可以了。

複數取倒數還是要用到尤拉公式。

void fft(comp *x, int n, int type) 

fft(l, n >> 1, type), fft(r, n >> 1, type);

comp wn1 = comp(cos(2 * pi / n), type * sin(2 * pi / n)), wnk = comp(1, 0);

for (int i = 0; i < (n >> 1); i++, wnk *= wn1;)

}

這份**常數巨大,不推薦使用。我們需要一種常數更小的寫法。

我們每次遞迴的時候都要將係數分成兩部分。為什麼不可以在計算前就將其計算好呢?

以 \(n=8\) 時為例:

原本係數 \(f=[0,1,2,3,4,5,6,7]\),變換後係數 \(\text(f)=[0,4,2,6,1,5,3,7]\)。

寫成二進位制後發現,變換後的下標即為原數寫成二進位制再翻轉後的數。

證明咕著。

for (int i = 0; i < len; i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) * (len >> 1));
使用時:

for (int i = 0; i < n; i++) if (i < rev[i]) swap(a[i], a[rev[i]]);
我們驚奇的發現這樣連遞迴都不需要了!

void fft(comp *x, int n, int tp) }}

}

Linux從入門到入土

在此開個專題,用來記錄一下在使用linux中常用的命令。持續更新 常見錯誤 dpkg i 檔名cmake dcmake install prefix usr include ar 解包 tar zxvf filename.tar 打包 tar czvf filename.tar dirname gz...

cmake 從入門到入土

你或許聽過好幾種 make 工具,例如 這些 make 工具遵循著不同的規範和標準,所執行的 makefile 格式也千差萬別。這樣就帶來了乙個嚴峻的問題 如果軟體想跨平台,必須要保證能夠在不同平台編譯。而如果使用上面的 make 工具,就得為每一種標準寫一次 makefile 這將是一件讓人抓狂的...

BZOJ從入門到入土

jsoi2010 連通數 有向圖求每乙個點到能到達的點的個數的和 包括自己到自己 scc bitset dp includeusing namespace std 方法 scc bitset傳遞閉包 或 直接bitset優化floyd 1.tarjan板子複習 2.tarjan縮點重建圖複習 3.利...