已知兩個多項式的係數表示式,求其乘積的係數表示式。
\[c_n = \sum\limits_^a_ib_
\]係數表示式逐項相乘,複雜度\(o(n^2)\),而點值表示式相乘複雜度為\(o(n)\)。因此我們要快速地將兩個多項式轉化為點值表示式,完成點值表示式的乘法,然後轉為係數表示式得到結果。表示式轉換的過程分別是離散傅利葉變換(\(dft\))和離散傅利葉逆變換(\(idft\))。這兩個過程統稱為快速傅利葉變換(\(fft\))。
目標:快速求得係數表示式對應的點值表示式。
設有多項式
\[a(x)=a_0+a_1x+a_2x^2+...+a_x^
\]其中\(n\)為2的冪,不足則補0。我們按照其指數的奇偶將其分為兩部分,變成兩個新多項式。
\[a_1(x)=a_0+a_2x+...+a_x^-1}
\]\[a_2(x)=a_1+a_3x+...+a_x^-1}
\]則有\(a(x)=a_1(x^2)+xa_2(x^2)\)
單位根我們選擇將單位根帶入這兩個子多項式求點值。在復平面內以原點為圓心,半徑為1的圓稱為單位圓。以\((1,0)\)為起點,將單位圓\(n\)等分,這些等分點是\(n\)個單位根。記第乙個單位根為\(\omega_n\),根據複數相乘模長相乘,幅角相加的原則,第\(k\)個單位根記作\(\omega_n^k\)。
根據三角函式推得單位根的表示方法:\(\omega_n^k=cos\frac+isin\frac\)
定理一(相消定理,折半引理):\(\omega_^=\omega_n^k,\omega_n^k=\omega_}^}\)
定理二:\(\omega_n^k=-\omega_n^}\)
設\(k<\dfrac\),將任意\(\omega_n^k\)代入\(a(x)\)可得$$a(\omega_nk)=a_1(\omega_})+\omega_nka_2(\omega_}k)$$
將任意\(\omega_n^}\)代入\(a(x)\)可得$$a(\omega_n})=a_1(\omega_})-\omega_nka_2(\omega_}k)$$
所以只要求出\(a_1(\omega_}^)\)和\(a_2(\omega_}^k)\),就可以求得\(a(\omega_n^k)\)和\(a(\omega_n^})\)。也就是說,\(k\)只要取\([0,\frac)\)就可以得出另一半\([\frac,n)\)。因此就得到了整個區間\([0,n)\),也就求出了\(a(x)\)的點值表示式。
遞迴求解即可,複雜度\(o(n \log n)\)
目標:將點值表示式轉化回係數表示式
設點值表示式乘法結果為\((y_0,y_1,...,y_)\)。設它對應的係數表示式(答案)為\((a_0,a_1,...,a_)\)
我們構造乙個以\((y_0,y_1,...,y_)\)為係數的多項式
\[b(x)=y_0+y_1x+...+y_x^
\]此時多項式\(b(x)\)是係數表示的。我們依次將單位根的倒數(\(\omega_n^0,\omega_n^,...,\omega_n^\))代入得到乙個點值表示式\((z_0,z_1,...,z_)\)。
\[\begin z_k &= \sum_^y_i(\omega_n^)^i\\ &= \sum_^(\sum_^a_j*\omega_n^)(\omega_n^)^i\\ &= \sum_^\sum_^a_j*(\omega_n^)^i\\ &= \sum_^a_j(\sum_^(\omega_n^)^i)\\ \end
\]而因為後半部分\(\sum_^(\omega_n^)^i\)可由等比數列求和公式得:
\[\sum_^(\omega_n^)^i=\dfrac)^n-1}-1}
\]注意,要使此式有意義,需滿足分母不為0。而當\(j=k\)時分母為0。因此分類討論:
當\(j \neq k\)時,\(\sum_^(\omega_n^)^i=0\)。
當\(j = k\)時,\(\sum_^(\omega_n^)^i=n\)
綜上\[z_k = \sum\limits_^a_j(\sum\limits_^(\omega_n^)^i) = na_k
\]所以
\[a_k=\dfrac
\]因此我們只需要把我們得到的點值表示式看做是係數表示式,再做一次\(dft\)(其中單位根以倒數形式代入)就能夠得到我們所要的結果了。最後還要除以\(n\)。
剛才這樣是通過遞迴實現的,常數較大。
如果能事先確定葉節點的值,就可以直接向上合併了。之前係數是按照奇偶分左右的。第\(i\)層的分配看二進位制的倒數第\(i\)位……因此係數的位置就是其初始位置的二進位制倒序。
當前這一輪我們需要利用兩個點值\(a_1(\omega_}^)\)和\(a_2(\omega_}^k)\),我們利用他們造出了\(a(\omega_n^k)\)並推出了\(a(\omega_n^})\),恰好可以把它們儲存到原來的位置。這就是所謂的「蝴蝶操作」。
\(fft\)需要用複數運算,存在精度問題。\(ntt\)(快速數論變換)是模意義下的\(fft\),可以避免精度問題,還快了不少。而其實現原理就是用模數的原根
來代替單位根。
設模數\(p\)的原根為\(g\),根據費馬小定理我們知道\(g^ \equiv 1 (\text p)\)。\(n\)個原根是\(g^*k}\)。這就要求\(n\)是\(p-1\)的因數,而\(n\)又是\(2\)的冪。\(998244353\)就是這樣乙個滿足條件的模數,它的原根是\(3\)。對於其他的模數,這裡有。
/*dennyqi 2019*/
#include #include #include #include using namespace std;
const int n = 4000010;
const int p = 998244353;
const int inf = 0x3f3f3f3f;
inline int max(const int& a, const int& b)
inline int min(const int& a, const int& b)
inline int sub(const int& a, const int& b)
inline int read()
int n,m,lim=1,len,invn,a[n],b[n],r[n];
inline int qpow(int x, int y)
return res;
}inline void ntt(int* a, int tp)
} }}int main()
學習筆記 多項式
給你n個點 x 1,y 1 x 2,y 2 求乙個n 1次的多項式 f x 求 f k 我們可以認為 f x f 1 x f 2 x f x 其中 f i x i y i 且 forall j neq i,f i x j 0 也就是說乙個點 x i 只在乙個函式中為 y i 其他函式中均為0 換言之...
學習筆記 多項式
把一直學不懂的各種大常數 o n log n 的神奇多項式演算法總結一下 證明什麼的比較簡略 還有我今天才知道預處理一下單位根會快很多 qwq 最沒用的乙個 首先我們能寫出乙個 o n 2 的暴力 這個你都不會就可以退役了 某位dalao題解中的 要確定乙個多項式,我們發現只要代 f 1 f 2 f...
多項式學習筆記
太菜了並不是很理解多項式,簡單記錄一下,緩慢更新吧 有問題問快速航 首先我們要求的柿子長這樣 c k sum a i b j 大概思路 先把兩個多項式轉成點值 dft 再把兩個多項式的點值乘在一起,把新的點值轉成多項式 idft 即可 首先要了解複數的運算 a b i c d i a c b d i...