在\(oi\)中,隨著時代的進步,我們常常會遇到要求兩個多項式卷積的情況。顯然,兩個多項式卷積時間複雜度是\(o(n^2)\),但這個速度遠遠不能滿足我們的需求。所以此時,我們就會用到\(fft\)。
\(fft\)全稱快速傅利葉變換。在\(oi\)中,通常用來在\(o(nlogn)\)時間裡解決兩個多項式卷積的問題。
和初中課本上的定義一樣。
乙個多項式有兩種表示方法,一種是係數表示法,一種是點值表示法。
係數表示法
係數表示法是我們最常用的表示方法。它定義\(a(x)=\sum_^ a_ix ^ i\)。然後我們就可以唯一確定乙個多項式。
點值表示法
對於乙個\(n\)次的多項式,我們發現將\(n+1\)個不同的數代入這個多項式,然後算出此時多項式的值。也可以確定這個多項式。可以感性理解一下,模擬如何確定乙個二次函式。
多項式之間的加減法非常簡單,就是對應次數的係數相加即可。都可以在\(o(n)\)時間內完成。
多項式卷積
你可以把兩個多項式卷積理解成是兩個多項式在做乘法。設兩個\(n\)次多項式\(a,b\)相乘,得到乙個\(2n\)次多項式\(c\),我們可以知道:
\(c(x)=\sum_^(\sum_a_ib_j)x^k\)
上文中講到兩個用係數表示的多項式直接卷積,時間複雜度是\(o(n^2)\)。那麼我們怎麼優化這個複雜度呢?觀察上文中的定義,我們發現當兩個用點值表示的多項式同時代入同乙個\(x\)時,對於每一位,結果多項式\(c(x)\)的點值就等於\(a(x)\)的點值乘上\(b(x)\)的點值。此時求出用點值表示的多項式\(c\)的時間複雜度顯然是\(o(n)\)的。
所以我們的問題就變成了如何快速實現係數表示法和點值表示法之間的轉化。我們發現如果點值如果代入單位根的話就有很多很好的性質。
在復平面上,作出單位圓。以原點為起點,單位圓的\(n\)等分點為終點,作出\(n\)個向量。將所得的幅角為正且最小的向量對應的複數稱為\(\omega_n\),也可以叫\(n\)次單位根。
性質一:\(\omega_^=\omega_n^k\)這個感性理解一下就好了,因為這兩個向量的終點顯然相同。
性質二:\(\omega_^}=-\omega_n^k\)可以想象一下這兩個向量在復平面上關於原點對稱。
性質三:\(\omega_^=\omega_^ = 1\)我們將乙個\(n\)個多項式\(a\),按照次數分成奇和偶兩組,並將乙個\(x\)提出後扔掉,分別記為\(a_1\)和\(a_2\)。舉乙個例子:對於多項式\(a(x)=2x^3+x^2+4x+3\),\(a_1(x)=2x^2+4\),\(a_2(x)=x+3\),所以我們發現\(a(x)=a_1(x^2)+x×a_2(x^2)\)。
設\(k<\frac\)我們將\(\omega_^\)代入\(a(x)\),可以得到如下式子:
\(a(\omega_^) \\= a_1(\omega_^)+\omega_^×a_2(\omega_^) \\= a_1(\omega_}^)+\omega_^×a_2(\omega_}^)\)
我們再將\(k+\frac\)代入式子,可以得到:
\(a(\omega_^})\\=a_1(\omega_^)+\omega_^} × a_2(\omega_^) \\= a_1(\omega_^ × \omega_ ^ ) - \omega_^ × a_2(\omega_^ × \omega_^) \\= a_1(\omega_^) - \omega_^ × a_2(\omega_^) \\= a_1(\omega_} ^ ) - \omega_ ^ × a_2(\omega_}^)\)
觀察這兩個式子,我們發現當我們將\(k\)取遍\([0,\frac)\)時,\(k+n\)取遍了\([\frac, n)\),所以當我們知道\(a_1(x)\)和\(a_2(x)\)在\(\omega_} ^ , \omega_} ^ ,\omega_} ^ \cdots \omega_} ^ - 1}\)的取值時,我們就可以知道\(a(x)\)在\(\omega_ ^ , \omega_ ^ ,\omega_ ^ \cdots \omega_ ^ \)的取值。求出\(a(n)\)時間複雜度是\(o(n)\)。
上面的過程都可以很方便地用遞迴分治來實現。通過主定理我們知道總的時間複雜度是\(o(nlogn)\)的。
如果用遞迴實現\(fft\)的話,常數很大,容易\(t\)掉。我們發現分治到邊界時下標等於原來下標的二進位制位翻轉。所以我們就可以用迭代實現這個過程。
// author: 23forever
#include #define pb push_back
#define pii pair#define mp make_pair
#define fi first
#define se second
typedef long long ll;
const int maxn = 4000000;
using namespace std;
typedef vector < double > poly;
typedef complex < double > comp;
typedef vector < comp > vc;
namespace polynomial
for (int i = 2; i <= len; i <<= 1) }}}
poly operator * (const poly vec_a, const poly vec_b)
vc a(len, 0), b(len, 0);
for (int i = 0; i < vec_a.size(); ++i) a[i] = vec_a[i];
for (int i = 0; i < vec_b.size(); ++i) b[i] = vec_b[i];
fft(a, 1);
fft(b, 1);
for (int i = 0; i < len; ++i) a[i] *= b[i];
fft(a, -1);
poly ret(tot, 0);
for (int i = 0; i < tot; ++i) ret[i] = a[i].real() / len;
return ret;}}
int n, m;
poly a, b;
void init()
int main()
#include typedef long long ll;
const int p = 998244353;
const int maxn = 400000;
const int maxl = 20;
using namespace std;
int fastpow(int b, int p)
return ret;
}typedef vector < ll > poly;
namespace polynomial
for (int i = 2, d = 1; i <= len; i <<= 1, ++d) }}}
poly operator * (poly a, poly b)
for (int i = 0; i < maxl; ++i) wn[i] = fastpow(g, (p - 1) >> i);
a.resize(len, 0), b.resize(len, 0);
ntt(a), ntt(b);
for (int i = 0; i < len; ++i) a[i] = a[i] * b[i] % p;
ntt(a);
for (int i = 1; i < len / 2; ++i) swap(a[i], a[len - i]);
ll inv = fastpow(len, p - 2);
for (int i = 0; i < len; ++i) a[i] = a[i] * inv % p;
while (a.size() > tot) a.pop_back();
return a;}}
int n, m;
poly a, b;
void init()
int main()
2 sat學習筆記(補檔
重學2 sat 給定乙個布林方程,判斷是否存在一組布林變數能滿足這個方程,方程的可行解被稱為 sat 這個問題是 np hard 的。但是如果我們對這個問題加上一些限制,就可以在多項式時間內求解了。設乙個布林方程為 a 1 lor a 2 cdots lor a n land b 1 lor b 2...
FFT學習筆記
fft可用於解決一些卷積問題。一般問題形式如下 c a b c i ij 0a i b i j 若把a,b看成兩個次數為n多項式 a x ni 0 a i xi,b x ni 0b i x i 原問題等於兩個多項式相乘,c的次數等於2n 1 乙個次數界為n的多項式a的點值表達為n個點值對所組成的集合...
FFT學習筆記
今天doggu講了,開始覺得這玩意好強啊 後來自己看的時候發現蠻zz wys表示 贊同 所以很多自己覺得遙不可及,一輩子都不可能學會的東西只要慢慢理解,理解好了再把他化為自己的語言,歸到自己的世界裡 就覺得不是很難了,甚至很簡單.當然開始並不是,從 好難啊 到 這麼簡單困大爺我這麼久,之前腦子抽了z...