1. 程式人生 > >多項式乘法(FFT)

多項式乘法(FFT)

1 前言

作為一名OI選手,至今未寫過fft相關的部落格,真是一大遺憾,這也導致我並沒有真正推過fft的所有式子
這一篇fft的部落格我將詳細介紹多項式乘法,易於理解,主要是為了等我啥時候忘了回來看,當然,一些公式會有些枯燥,如果是初學者請耐心看完哦,還有,畢竟這是手寫出來的,如果有錯誤,歡迎指正!

2 介紹

本欄用來普及一些知識和對FFT的思路進行描述
多項式乘法,顧名思義,首先是講到多項式,那麼什麼是多項式呢?

2.1 多項式

首先是多項式的定義,想必大家都知道(你上過初中吧),而在這裡,我們所說的多項式都是單個未知數x的
所以,在我們正常人眼中的一個次多項式就是形如 f

( x ) = a 0 x 0
+ a 1 x 1 +
+ a n 1 x n 1 f(x)=a_0x^0+a_1x^1+···+a_{n-1}x^{n-1}
沒錯,這就是大名鼎鼎的係數表示法
然後呢,由於在後面要用到,所以我在這裡再介紹一種點值表示法
就是將n個不同的值 x 0 , x 1 x n 1 x_0,x_1···x_{n-1} 分別帶入 f ( x ) f(x) ,獲得n個結果 y 0 , y 1 y n 1 y_0,y_1···y_{n-1} ,這n對數 ( x 0 , y 0 ) , ( x 1 , y 1 ) ( x n 1 , y n 1 ) (x_0,y_0),(x_1,y_1)···(x_{n-1},y_{n-1}) 就可以表示出這個多項式
看到這裡,如果你是初學者,你一定會感到非常迷茫,這為什麼對呢?
看到這裡,如果你是個FFT高手,你可能會感到迷茫,這為什麼對呢?
(dalao勿噴)
在這裡插入圖片描述
這張圖片裡係數表示法相當於是最右側的那個矩陣
而點值表示法則包含了左邊的兩個矩陣,可以通過這兩個矩陣計算出最右側的那個矩陣,所以兩種表示法是等價的
撒花
注:另外要說的是,由於演算法需要,本部落格所說的n次多項式都預設n是2的冪次(如果不足可以新增0來補)

2.2多項式的乘法

在做了那麼久的各種數學題後,我對多項式乘法有了有了的理解
對於一個一般的給定係數表示法的多項式乘法問題
比如兩個n次多項式A(x),B(x),給出係數表示法,求它們的乘積C(x)
分別列舉兩個多項式中的每一項,分別是 O ( n ) O(n)的 ,所以總複雜度為 O ( n 2 ) O(n^2)
這是一個很方便的做法

你可以發現一件很有趣的事情,那就是如果給出的是點值表示法,並且兩個多項式的x分別對應相等,那麼把y對應相乘,就能 O ( n ) O(n) 的獲取乘積的點值表示法

2.3 快速傅立葉變換(FFT)

那麼,FFT是用來幹什麼的呢?
對於一個多項式乘法問題,當給出係數表示法的時候, O ( n ) O(n) 的複雜度有時候並不足夠優越,而FFT就是一個能使多項式乘法做到 O n l o g n O(nlogn) 的一個演算法,具體的原理其實非常清晰

  • 兩個多項式的係數表示法
    求值,O(nlogn)
  • 兩個多項式的點值表示法
    點值乘法,O(n)
  • 兩個多項式乘積的點值表示法
    插值,O(nlogn)
  • 兩個多項式乘積的係數表示法

是不是一目瞭然呢?當然,要具體實現,還需要細細說來

3 實現

現在你已經大致知道FFT要幹什麼了,現在你已經會在點值情況下 O ( n ) O(n) 進行多項式乘法,剩下的就是要解決兩個問題——求值與插值了

3.1 暴力演算法( O ( n 3 ) O(n^3)

要先做題,必先暴力
首先是求值,加入你現在隨便找了n個互不相同的x,帶入其中,是什麼複雜度呢 O ( n 2 ) O(n^2)
然後是插值,有一個非常妙的方法,假設所有的a都是未知數,那麼這個問題就變成了經典的高斯消元問題,複雜度 O ( n 3 ) O(n^3)
不好意思,這兩個操作的複雜度都光榮的在 O ( n 2 ) O(n^2) 以上,使得當前這個演算法的總複雜度為 O ( n 3 ) O(n^3) 比文章開始的那個 O n 2 O(n^2) 都要差,不要灰心,既然複雜度不優,那就循序漸進的優化

3.2 離散傅立葉變換(通過優化使上面演算法複雜度降到 O ( n 2 ) O(n^2) ,請仔細看完,這是基礎)

你會發現,點值表示法有一個很好的特性,就是那個代入的x可以自己選擇
離散傅立葉變換的思路是將n個x的值取n個單位根(模長為一的複數)

複數(這是一個知識拓展框)

1 \sqrt{-1} 這個數,在實數範圍內是不存在的,所以拓展出複數這一概念, i = 1 i=\sqrt{-1} ,複數就是能夠被表示為 z = x + y i z=x+y*i 的數。所以對一個複數,可以用有序數對(x,y)表示,在座標軸上有對應的點,而這個複數就是從(0,0)到(x,y)的一條有向線段(只會向量的同學可以把它看成向量),而這個複數的模長就等於(0,0)到(x,y)的距離
由於複數是數,所以也有各種運算
加法:(a+bi)+(c+di)=(a+c)+(b+d)i
減法:(a+bi)-(c+di)=(a-c)+(b-d)i
乘法:(a+bi)*(c+di)=(ac-bd)+(ad+bc)i
當然,C++有專門的complex變數可以宣告,但是

不推薦使用!!!

為什麼呢?因為FFT本身就有一定的常數,如果再用系統complex常數會更大,所以推薦自己手寫struct

那麼什麼是單位根呢?

3.2.1 單位根

單位根所在的點是把單位圓(以原點為圓心,半徑為1的圓)從(0,1)開始平均分成n份的分割點
如下圖,這就是n=8時的單位圓,綠色圓上的紅點就是單位根所在的點
在這裡插入圖片描述
從(0,1)開始逆時針將這n個點編號,所表示的單位根分別為 w n 1 , w n 1 , w n n 1 w_n^1,w_n^1···,w_n^{n-1} ,特殊的, w n 1 w_n^1 被稱為n次單位根。容易發現每個單位根都非常好算,即 w n k = ( c o s k n 2 π , s i n k n 2 π ) w_n^k=(cos\frac{k}{n} 2π,sin\frac{k}{n} 2π)
這個用三角函式的想法非常好證
知道了這個之後,你會發現很多性質

性質1: w n k = ( w n 1 ) k w_n^k=(w_n^1)^k

證明:
    w n k w n 1 = ( c o s k n 2 π , s i n k n 2 π ) ( c o s 1 n 2 π , s i n 1 n 2 π ) = ( c o s k n 2 π c o s 1 n 2 π s i n k n 2 π s i n 1 n 2 π , s i n k n 2 π c o s 1 n 2 π + c o s k n 2 π s i n 1 n 2 π ) = ( c o s k + 1 n 2 π , s i n k + 1 n