快速傅里葉變換

多項式乘法

記多項式F(x),G(x) 進行乘法,得到一個新的多項式H(x) ,記CPoly(i) 表示Poly 這個多項式xi 這一項的係數是多少。

那麼CH(i)=ij=0CF(j)CG(ij)
我們也稱這種形式的乘法爲卷積。

樸素地計算H(x) 的時間複雜度顯然是O(n2) 的。
但是我們可以通過點值和插值運算,得到新多項式的值的信息,再得到新的多項式。


點值與插值

點值運算

一個n 次多項式f(x) 以由n+1 個點值對(x0,f(x0)),(x1,f(x1)),,(xn,f(xn)) 來表示,其中xi 各不相同。
因爲n+1 個點值對確定的n 次多項式是唯一的。

證明
記係數列向量AT=(c0,c1,,cn) , 行向量B=(f(x0),f(x1),,f(xn))
那麼B=VnA ,其中Vnn 階範德蒙德矩陣。
而範德蒙德矩陣可逆的充要條件是xi 互不相同。那麼這裏就可以求出唯一的逆矩陣V1n ,以及唯一的係數列向量A=BV1n

插值運算

插值運算是點值運算的逆運算。
用矩陣形式來表達則是A=BV1n

應用

假如我們可以通過某種手段獲得兩個多項式的點值表達,則他們的積的多項式的點值表達則可以O(n) 的求出來。假如我們可以通過某種手段實現插值運算,則多項式問題就迎刃而解了。

注意這裏假如兩個多項式的次數爲n ,那麼我們需要求2(n+1) 個點值對。因爲乘出來的多項式次數是2n 的。


N次複數根

定義

n 次單位複數根是滿足wn=1 的複數w ,記爲wn
n 次複數根恰好有n 個,從圖像上看它們平分了複平面上的單位圓。
數值上這些根爲
wkn=e2πkn
其中k=0,1,,n1
特別的,w0n=1

歐拉幅角公式

eiφ=cosφ+isinφ

證明
ex=1+x+x22!+x33!
sinx=xx33!+x55!
cosx=1x22!+x44!

eiφ=1+iφφ22!iφ33!+φ44!+
cosφ=1φ22!+φ44!
isinφ=iφiφ33!+iφ55!
eiφ=cosφ+isinφ

性質

以下的性質中可以將n 視作2 的冪,因爲實際操作時我們也是這麼做的。

複數的乘法對應旋轉,又由歐拉幅角公式可以看出這些複數的模長都爲1 ,於是就有

wkn=wk1nwn=(wn)k

因爲wnn=w0n=1 ,故

  • wpn=wpmodnn
  • wanwbn=w(a+b)modnn

消去引理wdkdn=wkn

證明
wdkdn=e2πdkdn=e2πkn=wnk

推論:

  • wn2n=w2=1
  • wn2+kn=wkn
  • (wkn)2=w2kn=wkn2

折半引理

n 次複數根的平方組成的集合恰好是n2 次複數根組成的集合

證明
wkn2=(wkn)2=(wk+n2n)2

求和引理

n1j=0(wkn)j=0

證明
n1j=0(wkn)j=0=1(wkn)n1wkn=1wknn1wkn=11k1wkn=0

注意這裏k 不能是n 的倍數,不然分母就爲0 了。


計算點值表達

如何快速地計算點值表達呢?

樸素地計算點值顯然是O(n2) 的。
但是我們可以巧妙地選取複數根作自變量的值,加速運算。

核心思想

我們選取n 次複數根作爲自變量的值來求點值表達。
那麼計算A(wkn)=c0+c1wkn++cn1(wkn)n1
A0(x)=c0+c2x2++c2kxk
A1(x)=c1+c3x++c2k+1xk
那麼A(wkn)=A0((wkn)2)+wknA1((wkn)2)
又根據折半引理,要求的自變量數減少了一半,但是總的多項式項數不變。

直到要求的自變量數只有一個,也就是w01=1 的時候,每個多項式的項數也只有一項常數項了,於是我們可以直接得到。根據上面的式子來遞歸計算就可以了。

具體實現

由於在實際實現程序的時候不能對整個數組進行復制,我們要考慮一下如何使用一些技巧來避免進行一整個數組的複製。

首先假如是遞歸地實現,大概是以下的流程。

  • 如果多項式只剩下一項了,那麼返回常數項。否則
  • 將多項式分成奇數項組成的多項式和偶數項組成的多項式,以當前所有自變量的平方作爲自變量遞歸計算兩個多項式的值
  • 對所有的自變量合併兩個分多項式所得的結果

注意到在每一層的自變量都是wkn 的形式,其中k 取遍0n1 。那麼我們就沒必要再開多餘的空間來記錄有哪些自變量。我們只需要簡單地知道多項式的項數n 就可以得到所有的自變量了。

於是遞歸版本大概長這個樣子

  • 如果多項式只剩下一項了,那麼返回常數項。否則
  • 將多項式分成奇數項組成的多項式和偶數項組成的多項式,以當前單位複數根的平方傳遞遞歸計算兩個多項式的值
  • 複數根的冪對應合併兩個分多項式所得的結果

但是這裏還是需要多開空間來存儲遞歸計算的結果,這一部分的處理也是這個算法比較巧妙的地方。

假如我們可以按照某種順序來儲存,並用某種順序計算,滿足
- 尋址快速(O(1))
- 線性空間
- 無後效性

那麼一個可行的算法就出來了。

一個簡單而又有效的方式,是按照它編號的二進制翻轉後的順序進行儲存係數和答案。(有趣的是,翻轉再翻轉就是本身。也就是說我們進行兩次這樣的運算以後我們得到的是原順序,之後會提到這個性質)
n=8 的時候,大概是這樣的一個順序:

0,4,2,6,1,5,3,7

而此時每一層存儲的答案數組的意義是不同的。假如當前正在處理第k[l,r=l+n2k1) 這個區間。那麼左半邊的答案數組fi(li<mid) 代表的實際上是[l,mid=l+n2k) 區間這個多項式,以wimod2kn2k 爲自變量的值。

首先來看第一次遞歸。顯然奇數項都在右邊,偶數項都在左邊。
第二次遞歸也是(編號整除二以後),每一次遞歸是將區間劈成兩半,左邊恰好是偶數項右邊恰好是奇數項。這個結果是顯然的,因爲二進制翻轉後最低位爲0 的編號總是排在左邊,最低位爲1 的編號總是排在右邊,相當於把當前這一層的奇數項和偶數項分開成爲各自連續的一段。
於是分開多項式的問題解決了。

接下來就是轉移的問題。因爲(wk2n)2=(wk+n2n)2 ,因此它們來自於上一層的轉移是相同的。我們將所有的求值兩兩分成一組來考慮,它們是相互獨立的。

A(wk2n)=A0(wkn)+wk2nA1(wkn)
A(wk+n2n)=A0(wkn)+wk+n2nA1(wkn)=A0(wkn)wknA1(wkn)

至此,層間的轉移就基本上解決了。

大致的程序實現應該是這樣的

  • 將所有的編號翻轉並建立起對應關係
  • 遞歸求值,記當前層求值的區間爲[l,r)
  • 如果項數爲1 ,將該位的值記爲常數項並返回
  • 否則遞歸兩側,得到一半的多項式的對應複數根爲自變量的值後,按照上面的式子得到這一層的值。具體來說
    1. 枚舉當前轉移的是哪一個分治區間。( 若當前每個區間的長度爲2k(k>0) ,那麼總共有n2k 組,每一組內有2k1 對轉移 )
    2. 枚舉當前分治區間是哪一個轉移,轉移之。

高效率的實現方法

注意不要搞混枚舉的順序,假如我們先枚舉了是第幾個轉移,然後再枚舉區間,就會使得內存的讀取十分的零散,使得效率大大降低。

至此,求點值的部分已經實現完畢了。


計算插值運算

理論部分

在實際操作的時候,點值運算和差值運算實際上相差不多。它相當於是通過將點值矩陣乘上範德蒙德矩陣的逆矩陣,然後得到係數矩陣。
而重點就在於得到範德蒙德矩陣的逆矩陣,不妨如下構造一個。

Vn×n 的範德蒙德矩陣,我們令V1i,j=1nVi,j 即可得到V 的逆矩陣V1

證明
主要是要證明V1V=E

  • ij 時,(V1V)i,j=n1k=0V1i,kVk,j
    Vk,j=(wjn)k,V1i,k=1nVi,k=1n(wkn)i=1n(win)k
    因此原式=n1k=0(wjn)k1n(win)k=1nn1k=0(wijn)k
    因爲wijn0 ,根據求和引理,原式=0
  • i=j 時,=1nn1k=0(wijn)k=1nn1k=01k=1

V1V=E ,證畢。

實際操作

程序實現的時候,我們並不需要重新實現一段求插值的代碼。因爲求點值和求插值十分的相似,我們只需要修改一小部分的參數就可以了。具體來說

  • n 次單位複數根爲wn1
  • 重新使用上述遞歸過程
  • 將結果的每一位除n

就完成了。


快速傅里葉變換

上述的點值表達插值運算綜合在一起就稱快速傅里葉變換(fast Fourier transfer)。
理論部分到此就結束了。
talk is cheap, show me your code.


延伸

  • 模意義下的FFT
    • 模數爲k2n+1 ,且是質數
    • 模數是質數
    • 模數是一般數
  • 快速威爾士變換(fast Walsh transform),即FFT的布爾運算形式
發佈了114 篇原創文章 · 獲贊 4 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章