多項式乘法
記多項式
那麼
我們也稱這種形式的乘法爲卷積。
樸素地計算
但是我們可以通過點值和插值運算,得到新多項式的值的信息,再得到新的多項式。
點值與插值
點值運算
一個
因爲
證明
記係數列向量AT=(c0,c1,⋯,cn) , 行向量B=(f(x0),f(x1),⋯,f(xn))
那麼B=VnA ,其中Vn 是n 階範德蒙德矩陣。
而範德蒙德矩陣可逆的充要條件是xi 互不相同。那麼這裏就可以求出唯一的逆矩陣V−1n ,以及唯一的係數列向量A=BV−1n
插值運算
插值運算是點值運算的逆運算。
用矩陣形式來表達則是
應用
假如我們可以通過某種手段獲得兩個多項式的點值表達,則他們的積的多項式的點值表達則可以
注意這裏假如兩個多項式的次數爲
N次複數根
定義
主
數值上這些根爲
其中
特別的,
歐拉幅角公式
證明
ex=1+x+x22!+x33!⋯
sinx=x−x33!+x55!⋯
cosx=1−x22!+x44!⋯
eiφ=1+iφ−φ22!−iφ33!+φ44!+⋯
cosφ=1−φ22!+φ44!⋯
isinφ=iφ−iφ33!+iφ55!⋯
eiφ=cosφ+isinφ
性質
以下的性質中可以將
複數的乘法對應旋轉,又由歐拉幅角公式可以看出這些複數的模長都爲
因爲
wpn=wpmodnn wanwbn=w(a+b)modnn
消去引理:
證明
wdkdn=e2πdkdn=e2πkn=wnk
推論:
wn2n=w2=−1 wn2+kn=−wkn (wkn)2=w2kn=wkn2
折半引理
證明
wkn2=(wkn)2=(wk+n2n)2
求和引理
證明
∑n−1j=0(wkn)j=0=1−(wkn)n1−wkn=1−wknn1−wkn=1−1k1−wkn=0
注意這裏
計算點值表達
如何快速地計算點值表達呢?
樸素地計算點值顯然是
但是我們可以巧妙地選取複數根作自變量的值,加速運算。
核心思想
我們選取
那麼計算
令
那麼
又根據折半引理,要求的自變量數減少了一半,但是總的多項式項數不變。
直到要求的自變量數只有一個,也就是
具體實現
由於在實際實現程序的時候不能對整個數組進行復制,我們要考慮一下如何使用一些技巧來避免進行一整個數組的複製。
首先假如是遞歸地實現,大概是以下的流程。
- 如果多項式只剩下一項了,那麼返回常數項。否則
- 將多項式分成奇數項組成的多項式和偶數項組成的多項式,以當前所有自變量的平方作爲自變量遞歸計算兩個多項式的值
- 對所有的自變量合併兩個分多項式所得的結果
注意到在每一層的自變量都是
於是遞歸版本大概長這個樣子
- 如果多項式只剩下一項了,那麼返回常數項。否則
- 將多項式分成奇數項組成的多項式和偶數項組成的多項式,以當前單位複數根的平方傳遞遞歸計算兩個多項式的值
- 對複數根的冪對應合併兩個分多項式所得的結果
但是這裏還是需要多開空間來存儲遞歸計算的結果,這一部分的處理也是這個算法比較巧妙的地方。
假如我們可以按照某種順序來儲存,並用某種順序計算,滿足
- 尋址快速
- 線性空間
- 無後效性
那麼一個可行的算法就出來了。
一個簡單而又有效的方式,是按照它編號的二進制翻轉後的順序進行儲存係數和答案。(有趣的是,翻轉再翻轉就是本身。也就是說我們進行兩次這樣的運算以後我們得到的是原順序,之後會提到這個性質)
當
而此時每一層存儲的答案數組的意義是不同的。假如當前正在處理第
首先來看第一次遞歸。顯然奇數項都在右邊,偶數項都在左邊。
第二次遞歸也是(編號整除二以後),每一次遞歸是將區間劈成兩半,左邊恰好是偶數項右邊恰好是奇數項。這個結果是顯然的,因爲二進制翻轉後最低位爲
於是分開多項式的問題解決了。
接下來就是轉移的問題。因爲
至此,層間的轉移就基本上解決了。
大致的程序實現應該是這樣的
- 將所有的編號翻轉並建立起對應關係
- 遞歸求值,記當前層求值的區間爲
[l,r) - 如果項數爲
1 ,將該位的值記爲常數項並返回 - 否則遞歸兩側,得到一半的多項式的對應複數根爲自變量的值後,按照上面的式子得到這一層的值。具體來說
- 枚舉當前轉移的是哪一個分治區間。( 若當前每個區間的長度爲
2k(k>0) ,那麼總共有n2k 組,每一組內有2k−1 對轉移 ) - 枚舉當前分治區間是哪一個轉移,轉移之。
- 枚舉當前轉移的是哪一個分治區間。( 若當前每個區間的長度爲
高效率的實現方法
注意不要搞混枚舉的順序,假如我們先枚舉了是第幾個轉移,然後再枚舉區間,就會使得內存的讀取十分的零散,使得效率大大降低。
至此,求點值的部分已經實現完畢了。
計算插值運算
理論部分
在實際操作的時候,點值運算和差值運算實際上相差不多。它相當於是通過將點值矩陣乘上範德蒙德矩陣的逆矩陣,然後得到係數矩陣。
而重點就在於得到範德蒙德矩陣的逆矩陣,不妨如下構造一個。
令
證明
主要是要證明V−1V=E
- 當
i≠j 時,(V−1V)i,j=∑n−1k=0V−1i,kVk,j
又Vk,j=(wjn)k,V−1i,k=1nVi,k=1n(wkn)i=1n(win)k
因此原式=∑n−1k=0(wjn)k⋅1n(win)k=1n∑n−1k=0(wi−jn)k
因爲wi−jn≠0 ,根據求和引理,原式=0 - 當
i=j 時,原式=1n∑n−1k=0(wi−jn)k=1n∑n−1k=01k=1 故
V−1V=E ,證畢。
實際操作
程序實現的時候,我們並不需要重新實現一段求插值的代碼。因爲求點值和求插值十分的相似,我們只需要修改一小部分的參數就可以了。具體來說
- 令
n 次單位複數根爲w−n1 - 重新使用上述遞歸過程
- 將結果的每一位除
n
就完成了。
快速傅里葉變換
上述的點值表達和插值運算綜合在一起就稱快速傅里葉變換(fast Fourier transfer)。
理論部分到此就結束了。
talk is cheap, show me your code.
延伸
- 模意義下的FFT
- 模數爲
k2n+1 ,且是質數 - 模數是質數
- 模數是一般數
- 模數爲
- 快速威爾士變換(fast Walsh transform),即FFT的布爾運算形式