離散傅里葉(DFT) 與 快速傅里葉(FFT)

這篇文章的核心思想就是:

要讓讀者在不看任何數學公式的情況下理解傅里葉。

傅里葉分析不僅僅是一個數學工具,更是一種可以徹底顛覆一個人以前世界觀的思維模式。但不幸的是,傅里葉分析的公式看起來太複雜了,所以很多大一新生上來就懵圈並從此對它深惡痛絕。老實說,這麼有意思的東西居然成了大學裏的殺手課程,不得不歸咎於編教材的人實在是太嚴肅了。(您把教材寫得好玩一點會死嗎?會死嗎?)所以我一直想寫一個有意思的文章來解釋傅里葉分析,有可能的話高中生都能看懂的那種。所以,不管讀到這裏的您從事何種工作,我保證您都能看懂,並且一定將體會到通過傅里葉分析看到世界另一個樣子時的快感。至於對於已經有一定基礎的朋友,也希望不要看到會的地方就急忙往後翻,仔細讀一定會有新的發現。

————以上是定場詩————

下面進入正題:

抱歉,還是要囉嗦一句:其實學習本來就不是易事,我寫這篇文章的初衷也是希望大家學習起來更加輕鬆,充滿樂趣。但是千萬!千萬不要把這篇文章收藏起來,或是存下地址,心裏想着:以後有時間再看。這樣的例子太多了,也許幾年後你都沒有再打開這個頁面。無論如何,耐下心,讀下去。這篇文章要比讀課本要輕鬆、開心得多……

p.s.本文無論是cos還是sin,都統一用“正弦波”(Sine Wave)一詞來代表簡諧波。

一、什麼是頻域

從我們出生,我們看到的世界都以時間貫穿,股票的走勢、人的身高、汽車的軌跡都會隨着時間發生改變。這種以時間作爲參照來觀察動態世界的方法我們稱其爲時域分析。而我們也想當然的認爲,世間萬物都在隨着時間不停的改變,並且永遠不會靜止下來。但如果我告訴你,用另一種方法來觀察世界的話,你會發現世界是永恆不變的,你會不會覺得我瘋了?我沒有瘋,這個靜止的世界就叫做頻域。

先舉一個公式上並非很恰當,但意義上再貼切不過的例子:

在你的理解中,一段音樂是什麼呢?


這是我們對音樂最普遍的理解,一個隨着時間變化的震動。但我相信對於樂器小能手們來說,音樂更直觀的理解是這樣的:

好的!下課,同學們再見。

是的,其實這一段寫到這裏已經可以結束了。上圖是音樂在時域的樣子,而下圖則是音樂在頻域的樣子。所以頻域這一概念對大家都從不陌生,只是從來沒意識到而已。

現在我們可以回過頭來重新看看一開始那句癡人說夢般的話:世界是永恆的。

將以上兩圖簡化:

時域:

頻域:

在時域,我們觀察到鋼琴的琴絃一會上一會下的擺動,就如同一支股票的走勢;而在頻域,只有那一個永恆的音符。

所以

你眼中看似落葉紛飛變化無常的世界,實際只是躺在上帝懷中一份早已譜好的樂章。

抱歉,這不是一句雞湯文,而是黑板上確鑿的公式:傅里葉同學告訴我們,任何周期函數,都可以看作是不同振幅,不同相位正弦波的疊加。在第一個例子裏我們可以理解爲,利用對不同琴鍵不同力度,不同時間點的敲擊,可以組合出任何一首樂曲。

而貫穿時域與頻域的方法之一,就是傳中說的傅里葉分析。傅里葉分析可分爲傅里葉級數(Fourier Serie)和傅里葉變換(Fourier Transformation),我們從簡單的開始談起。

二、傅里葉級數(Fourier Series)的頻譜

還是舉個栗子並且有圖有真相纔好理解。

如果我說我能用前面說的正弦曲線波疊加出一個帶90度角的矩形波來,你會相信嗎?你不會,就像當年的我一樣。但是看看下圖:


第一幅圖是一個鬱悶的正弦波cos(x)

第二幅圖是2個賣萌的正弦波的疊加cos(x)+a.cos(3x)

第三幅圖是4個發春的正弦波的疊加

第四幅圖是10個便祕的正弦波的疊加

隨着正弦波數量逐漸的增長,他們最終會疊加成一個標準的矩形,大家從中體會到了什麼道理?

(只要努力,彎的都能掰直!)

隨着疊加的遞增,所有正弦波中上升的部分逐漸讓原本緩慢增加的曲線不斷變陡,而所有正弦波中下降的部分又抵消了上升到最高處時繼續上升的部分使其變爲水平線。一個矩形就這麼疊加而成了。但是要多少個正弦波疊加起來才能形成一個標準90度角的矩形波呢?不幸的告訴大家,答案是無窮多個。(上帝:我能讓你們猜着我?)

不僅僅是矩形,你能想到的任何波形都是可以如此方法用正弦波疊加起來的。這是沒
有接觸過傅里葉分析的人在直覺上的第一個難點,但是一旦接受了這樣的設定,遊戲就開始有意思起來了。

還是上圖的正弦波累加成矩形波,我們換一個角度來看看:

在這幾幅圖中,最前面黑色的線就是所有正弦波疊加而成的總和,也就是越來越接近矩形波的那個圖形。而後面依不同顏色排列而成的正弦波就是組合爲矩形波的各個分量。這些正弦波按照頻率從低到高從前向後排列開來,而每一個波的振幅都是不同的。一定有細心的讀者發現了,每兩個正弦波之間都還有一條直線,那並不是分割線,而是振幅爲0的正弦波!也就是說,爲了組成特殊的曲線,有些正弦波成分是不需要的。

這裏,不同頻率的正弦波我們成爲頻率分量。

好了,關鍵的地方來了!!

如果我們把第一個頻率最低的頻率分量看作“1”,我們就有了構建頻域的最基本單元。

對於我們最常見的有理數軸,數字“1”就是有理數軸的基本單元。

時域的基本單元就是“1秒”,如果我們將一個角頻率爲的正弦波cos(t)看作基礎,那麼頻域的基本單元就是

有了“1”,還要有“0”才能構成世界,那麼頻域的“0”是什麼呢?cos(0t)就是一個週期無限長的正弦波,也就是一條直線!所以在頻域,0頻率也被稱爲直流分量,在傅里葉級數的疊加中,它僅僅影響全部波形相對於數軸整體向上或是向下而不改變波的形狀。

接下來,讓我們回到初中,回憶一下已經死去的八戒,啊不,已經死去的老師是怎麼定義正弦波的吧。

正弦波就是一個圓周運動在一條直線上的投影。所以頻域的基本單元也可以理解爲一個始終在旋轉的圓:


介紹完了頻域的基本組成單元,我們就可以看一看一個矩形波,在頻域裏的另一個模樣了:

這是什麼奇怪的東西?

這就是矩形波在頻域的樣子,是不是完全認不出來了?教科書一般就給到這裏然後留給了讀者無窮的遐想,以及無窮的吐槽,其實教科書只要補一張圖就足夠了:頻域圖像,也就是俗稱的頻譜,就是——


再清楚一點:

可以發現,在頻譜中,偶數項的振幅都是0,也就對應了圖中的彩色直線。振幅爲0的正弦波。

老實說,在我學傅里葉變換時,維基的這個圖還沒有出現,那時我就想到了這種表達方法,而且,後面還會加入維基沒有表示出來的另一個譜——相位譜。

但是在講相位譜之前,我們先回顧一下剛剛的這個例子究竟意味着什麼。記得前面說過的那句“世界是靜止的”嗎?估計好多人對這句話都已經吐槽半天了。想象一下,世界上每一個看似混亂的表象,實際都是一條時間軸上不規則的曲線,但實際這些曲線都是由這些無窮無盡的正弦波組成。我們看似不規律的事情反而是規律的正弦波在時域上的投影,而正弦波又是一個旋轉的圓在直線上的投影。那麼你的腦海中會產生一個什麼畫面呢?

我們眼中的世界就像皮影戲的大幕布,幕布的後面有無數的齒輪,大齒輪帶動小齒輪,小齒輪再帶動更小的。在最外面的小齒輪上有一個小人——那就是我們自己。我們只看到這個小人毫無規律的在幕布前表演,卻無法預測他下一步會去哪。而幕布後面的齒輪卻永遠一直那樣不停的旋轉,永不停歇。這樣說來有些宿命論的感覺。說實話,這種對人生的描繪是我一個朋友在我們都是高中生的時候感嘆的,當時想想似懂非懂,直到有一天我學到了傅里葉級數……

三、傅里葉級數(Fourier Series)的相位譜

上一章的關鍵詞是:從側面看。這一章的關鍵詞是:從下面看。

在這一章最開始,我想先回答很多人的一個問題:傅里葉分析究竟是幹什麼用的?這段相對比較枯燥,已經知道了的同學可以直接跳到下一個分割線。

先說一個最直接的用途。無論聽廣播還是看電視,我們一定對一個詞不陌生——頻道。頻道頻道,就是頻率的通道,不同的頻道就是將不同的頻率作爲一個通道來進行信息傳輸。下面大家嘗試一件事:

先在紙上畫一個sin(x),不一定標準,意思差不多就行。不是很難吧。

好,接下去畫一個sin(3x)+sin(5x)的圖形。

別說標準不標準了,曲線什麼時候上升什麼時候下降你都不一定畫的對吧?

好,畫不出來不要緊,我把sin(3x)+sin(5x)的曲線給你,但是前提是你不知道這個曲線的方程式,現在需要你把sin(5x)給我從圖裏拿出去,看看剩下的是什麼。這基本是不可能做到的。

但是在頻域呢?則簡單的很,無非就是幾條豎線而已。

所以很多在時域看似不可能做到的數學操作,在頻域相反很容易。這就是需要傅里葉變換的地方。尤其是從某條曲線中去除一些特定的頻率成分,這在工程上稱爲濾波,是信號處理最重要的概念之一,只有在頻域才能輕鬆的做到。

再說一個更重要,但是稍微複雜一點的用途——求解微分方程。(這段有點難度,看不懂的可以直接跳過這段)微分方程的重要性不用我過多介紹了。各行各業都用的到。但是求解微分方程卻是一件相當麻煩的事情。因爲除了要計算加減乘除,還要計算微分積分。而傅里葉變換則可以讓微分和積分在頻域中變爲乘法和除法,大學數學瞬間變小學算術有沒有。

傅里葉分析當然還有其他更重要的用途,我們隨着講隨着提。

————————————————————————————————————

下面我們繼續說相位譜:

通過時域到頻域的變換,我們得到了一個從側面看的頻譜,但是這個頻譜並沒有包含時域中全部的信息。因爲頻譜只代表每一個對應的正弦波的振幅是多少,而沒有提到相位。基礎的正弦波A.sin(wt+θ)中,振幅,頻率,相位缺一不可,不同相位決定了波的位置,所以對於頻域分析,僅僅有頻譜(振幅譜)是不夠的,我們還需要一個相位譜。那麼這個相位譜在哪呢?我們看下圖,這次爲了避免圖片太混論,我們用7個波疊加的圖。

鑑於正弦波是週期的,我們需要設定一個用來標記正弦波位置的東西。在圖中就是那些小紅點。小紅點是距離頻率軸最近的波峯,而這個波峯所處的位置離頻率軸有多遠呢?爲了看的更清楚,我們將紅色的點投影到下平面,投影點我們用粉色點來表示。當然,這些粉色的點只標註了波峯距離頻率軸的距離,並不是相位。

這裏需要糾正一個概念:時間差並不是相位差。如果將全部週期看作2Pi或者360度的話,相位差則是時間差在一個週期中所佔的比例。我們將時間差除週期再乘2Pi,就得到了相位差。

在完整的立體圖中,我們將投影得到的時間差依次除以所在頻率的週期,就得到了最下面的相位譜。所以,頻譜是從側面看,相位譜是從下面看。下次偷看女生裙底被發現的話,可以告訴她:“對不起,我只是想看看你的相位譜。”

注意到,相位譜中的相位除了0,就是Pi。因爲cos(t+Pi)=-cos(t),所以實際上相位爲Pi的波只是上下翻轉了而已。對於週期方波的傅里葉級數,這樣的相位譜已經是很簡單的了。另外值得注意的是,由於cos(t+2Pi)=cos(t),所以相位差是週期的,pi和3pi,5pi,7pi都是相同的相位。人爲定義相位譜的值域爲(-pi,pi],所以圖中的相位差均爲Pi。

最後來一張大集合:

四、傅里葉變換(Fourier Transformation)

相信通過前面三章,大家對頻域以及傅里葉級數都有了一個全新的認識。但是文章在一開始關於鋼琴琴譜的例子我曾說過,這個栗子是一個公式錯誤,但是概念典型的例子。所謂的公式錯誤在哪裏呢?

傅里葉級數的本質是將一個週期的信號分解成無限多分開的(離散的)正弦波,但是宇宙似乎並不是週期的。曾經在學數字信號處理的時候寫過一首打油詩:

往昔連續非週期,

回憶週期不連續,

任你ZT、DFT,

還原不回去。

(請無視我渣一樣的文學水平……)

在這個世界上,有的事情一期一會,永不再來,並且時間始終不曾停息地將那些刻骨銘心的往昔連續的標記在時間點上。但是這些事情往往又成爲了我們格外寶貴的回憶,在我們大腦裏隔一段時間就會週期性的蹦出來一下,可惜這些回憶都是零散的片段,往往只有最幸福的回憶,而平淡的回憶則逐漸被我們忘卻。因爲,往昔是一個連續的非週期信號,而回憶是一個週期離散信號。

是否有一種數學工具將連續非週期信號變換爲週期離散信號呢?抱歉,真沒有。

比如傅里葉級數,在時域是一個週期且連續的函數,而在頻域是一個非週期離散的函數。這句話比較繞嘴,實在看着費事可以乾脆回憶第一章的圖片。

而在我們接下去要講的傅里葉變換,則是將一個時域非週期的連續信號,轉換爲一個在頻域非週期的連續信號。

算了,還是上一張圖方便大家理解吧:



或者我們也可以換一個角度理解:傅里葉變換實際上是對一個週期無限大的函數進行傅里葉變換。

所以說,鋼琴譜其實並非一個連續的頻譜,而是很多在時間上離散的頻率,但是這樣的一個貼切的比喻真的是很難找出第二個來了。

因此在傅里葉變換在頻域上就從離散譜變成了連續譜。那麼連續譜是什麼樣子呢?

你見過大海麼?

爲了方便大家對比,我們這次從另一個角度來看頻譜,還是傅里葉級數中用到最多的那幅圖,我們從頻率較高的方向看。



以上是離散譜,那麼連續譜是什麼樣子呢?

盡情的發揮你的想象,想象這些離散的正弦波離得越來越近,逐漸變得連續……

直到變得像波濤起伏的大海:

很抱歉,爲了能讓這些波浪更清晰的看到,我沒有選用正確的計算參數,而是選擇了一些讓圖片更美觀的參數,不然這圖看起來就像屎一樣了。

不過通過這樣兩幅圖去比較,大家應該可以理解如何從離散譜變成了連續譜的了吧?原來離散譜的疊加,變成了連續譜的累積。所以在計算上也從求和符號變成了積分符號。

不過,這個故事還沒有講完,接下去,我保證讓你看到一幅比上圖更美麗壯觀的圖片,但是這裏需要介紹到一個數學工具才能然故事繼續,這個工具就是——

五、宇宙耍帥第一公式:歐拉公式

虛數i這個概念大家在高中就接觸過,但那時我們只知道它是-1的平方根,可是它真正的意義是什麼呢?


這裏有一條數軸,在數軸上有一個紅色的線段,它的長度是1。當它乘以3的時候,它的長度發生了變化,變成了藍色的線段,而當它乘以-1的時候,就變成了綠色的線段,或者說線段在數軸上圍繞原點旋轉了180度。

我們知道乘-1其實就是乘了兩次 i使線段旋轉了180度,那麼乘一次 i 呢——答案很簡單——旋轉了90度。



同時,我們獲得了一個垂直的虛數軸。實數軸與虛數軸共同構成了一個複數的平面,也稱複平面。這樣我們就瞭解到,乘虛數i的一個功能——旋轉。

現在,就有請宇宙第一耍帥公式歐拉公式隆重登場——


這個公式在數學領域的意義要遠大於傅里葉分析,但是乘它爲宇宙第一耍帥公式是因爲它的特殊形式——當x等於Pi的時候。


經常有理工科的學生爲了跟妹子表現自己的學術功底,用這個公式來給妹子解釋數學之美:”石榴姐你看,這個公式裏既有自然底數e,自然數1和0,虛數i還有圓周率pi,它是這麼簡潔,這麼美麗啊!“但是姑娘們心裏往往只有一句話:”臭屌絲……“

這個公式關鍵的作用,是將正弦波統一成了簡單的指數形式。我們來看看圖像上的涵義:



歐拉公式所描繪的,是一個隨着時間變化,在複平面上做圓周運動的點,隨着時間的改變,在時間軸上就成了一條螺旋線。如果只看它的實數部分,也就是螺旋線在左側的投影,就是一個最基礎的餘弦函數。而右側的投影則是一個正弦函數。

關於複數更深的理解,大家可以參考:

複數的物理意義是什麼?

這裏不需要講的太複雜,足夠讓大家理解後面的內容就可以了。

六、指數形式的傅里葉變換

有了歐拉公式的幫助,我們便知道:正弦波的疊加,也可以理解爲螺旋線的疊加在實數空間的投影。而螺旋線的疊加如果用一個形象的栗子來理解是什麼呢?

光波

高中時我們就學過,自然光是由不同顏色的光疊加而成的,而最著名的實驗就是牛頓師傅的三棱鏡實驗:



所以其實我們在很早就接觸到了光的頻譜,只是並沒有瞭解頻譜更重要的意義。

但不同的是,傅里葉變換出來的頻譜不僅僅是可見光這樣頻率範圍有限的疊加,而是頻率從0到無窮所有頻率的組合。

這裏,我們可以用兩種方法來理解正弦波:

第一種前面已經講過了,就是螺旋線在實軸的投影。

另一種需要藉助歐拉公式的另一種形式去理解:

e^{it}=cos(t)+i.sin(t)
e^{-it}=cos(t)-i.sin(t)

將以上兩式相加再除2,得到:

cos(t)=\frac{e^{it}+e^{-it}}{2}

這個式子可以怎麼理解呢?

我們剛纔講過,e^(it)可以理解爲一條逆時針旋轉的螺旋線,那麼e^(-it)則可以理解爲一條順時針旋轉的螺旋線。而cos(t)則是這兩條旋轉方向不同的螺旋線疊加的一半,因爲這兩條螺旋線的虛數部分相互抵消掉了!

舉個例子的話,就是極化方向不同的兩束光波,磁場抵消,電場加倍。

這裏,逆時針旋轉的我們稱爲正頻率,而順時針旋轉的我們稱爲負頻率(注意不是複頻率)。

好了,剛纔我們已經看到了大海——連續的傅里葉變換頻譜,現在想一想,連續的螺旋線會是什麼樣子:

想象一下再往下翻:

|

|

|

|

|

|

|

|

|

|

|

|

|

是不是很漂亮?

你猜猜,這個圖形在時域是什麼樣子?

哈哈,是不是覺得被狠狠扇了一個耳光。數學就是這麼一個把簡單的問題搞得很複雜的東西。

順便說一句,那個像大海螺一樣的圖,爲了方便觀看,我僅僅展示了其中正頻率的部分,負頻率的部分沒有顯示出來。

如果你認真去看,海螺圖上的每一條螺旋線都是可以清楚的看到的,每一條螺旋線都有着不同的振幅(旋轉半徑),頻率(旋轉週期)以及相位。而將所有螺旋線連成平面,就是這幅海螺圖了。

好了,講到這裏,相信大家對傅里葉變換以及傅里葉級數都有了一個形象的理解了,我們最後用一張圖來總結一下:


老實說,數學工具對於工科生和對於理科生來說,意義是完全不同的。工科生只要理解了,會用,會查,就足夠了。但是很多高校卻將這些重要的數學課程教給數學系的老師去教。這樣就出現一個問題,數學老師講得天花亂墜,又是推理又是證明,但是學生心裏就只有一句話:學這貨到底幹嘛用的?

缺少了目標的教育是徹底的失敗。

在開始學習一門數學工具的時候,學生完全不知道這個工具的作用,現實涵義。而教材上有隻有晦澀難懂,定語就二十幾個字的概念以及看了就眼暈的公式。能學出興趣來就怪了!

好在我很幸運,遇到了大連海事大學的吳楠老師。他的課全程來看是兩條線索,一條從上而下,一條從下而上。先講本門課程的意義,然後指出這門課程中會遇到哪樣的問題,讓學生知道自己學習的某種知識在現實中扮演的角色。然後再從基礎講起,梳理知識樹,直到延伸到另一條線索中提出的問題,完美的銜接在一起!

最後,祝大家都能在學習中找到樂趣。

查看原文



快速傅里葉(FFT)

FFT正如名字的含義,快速計算傅里葉變換。據測試,快速傅里葉代碼的執行時間比傅里葉代碼要快1000倍。


1.多項式

        多項式的兩種表達方式:係數表達和點值表達

係數表達就是大家常用的表達方式,點值表達就像在這個多項式函數上取n個不同的點,這樣就可以確定原多項式。

比如說二次函數需要3個點就可以確定,一次函數需要2個點,一個n次多項式需要n個點(n次多項式意思是有0..n-1次冪的多項式)

A(x)=x^2+2*x-1可以被表達爲{  ( 0 , -1 ) , ( 1 , 2 ) , ( 2 , 7 )  }

加法和乘法:

B(x)=x^2-x+2  { ( 0 , 2 ) , ( 1 , 2 ) , ( 2 , 4 ) }

     C(x)=A(x)+B(x)=2x^2+x+1   { ( 0, 1) , ( 1 , 4 ) , ( 2, 11 ) }

     注意乘法需要2n個點 lz比較懶就不寫了……


於是我們得到一個計算多項式的方法:

 


2.n次單位複數根

   

有關複數根的性質可以百度到,不再贅述

http://baike.baidu.com/link?url=017EPfseoBwVxWpWPm5aunUn8x9dmRvioav9IubYLSKEGngK8_rDV2bd4PFCM8sJ



3.DFT&&FFT

使用單位根計算點值表達式叫DFT(離散傅里葉變換)複雜度n^2,FFT是其優化版複雜度nlogn


計算FFT的僞代碼(好吧用的是python)

下劃線代表的是下標,括號代表上標,for 循環的range是左閉右開的

[python] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. FFT(a):  
  2.     n=a.length()  
  3.     if n==1:  
  4.         return a  
  5.     w_n=e^(pi*i/n)=complex(cos(2*pi/n),sin(2*pi/n))  
  6.     w=1  
  7.     a(0)=[a0,a2,...a_n-2]  
  8.     a(1)=[a1,a3,...a_n-1]  
  9.     y(0)=FFT(a(0))  
  10.     y(1)=FFT(a(1))  
  11.     for k in range(0,n/2):  
  12.         y_k=y_k(0)+w*y_k(1)  
  13.         y_k+n/2=y_k(0)-w*y_k(1)  
  14.         w=w*w_n  
  15.     return y  

4.遞歸=>迭代??


FFT的for循環中有兩次w_n^k*y_k(1)的計算,於是可以改寫成這樣

 

[python] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. for k in range(0,n/2):  
  2.     t=w*y_k(1)  
  3.     y_k=y_k(0)+t  
  4.     y_k+n/2=y_k(0)-t  
  5.     w=w*w_n  
  6. #這一過程被稱蝴蝶操作  

觀察每次按照奇偶位置分割所形成的樹:

每個數和他二進制相反的位置互換!!

僞代碼

[python] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. BIT-REVERSE-COPY(a,A):  
  2.     n=a.length()  
  3.     for k in range(0,n):  
  4.         A[rev(k)]=a_k  
  5. #網上說rev函數很好寫,就沒寫……  

於是我們給出FFT的迭代實現的僞代碼:

[python] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. FFT(a):  
  2.     BIT-REVERSE-COPY(a,A)  
  3.     n=a.length()  
  4.     for s in range(1,log2(n)+1):  
  5.         m=2^s  
  6.         w_m=e^(2*pi*i/m)=complex(cos(2*pi*m),sin(2*pi*m))  
  7.         for k in range(0,n,m):  
  8.             w=1  
  9.             for j in range(0,m/2):  
  10.                 t=w*A[k+j+m/2]  
  11.                 u=A[k+j]  
  12.                 A[k+j]=u+t  
  13.                 A[k+j+m/2]=u-t  
  14.                 w=w*w_m  
  15.     return A  
差不多講完了,最後給出C++代碼

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. #include<bitset>  
  2. #include <cstdio>  
  3. #include <cstring>  
  4. #include <cmath>  
  5. #include <algorithm>  
  6. #define N 400005  
  7. #define pi acos(-1.0) // PI值  
  8. using namespace std;  
  9. struct complex  
  10. {  
  11.     double r,i;  
  12.     complex(double real=0.0,double image=0.0){  
  13.         r=real; i=image;  
  14.     }  
  15.     // 以下爲三種虛數運算的定義  
  16.     complex operator + (const complex o){  
  17.         return complex(r+o.r,i+o.i);  
  18.     }  
  19.     complex operator - (const complex o){  
  20.         return complex(r-o.r,i-o.i);  
  21.     }  
  22.     complex operator * (const complex o){  
  23.         return complex(r*o.r-i*o.i,r*o.i+i*o.r);  
  24.     }  
  25. }x1[N],x2[N];  
  26. char a[N/2],b[N/2];  
  27. int sum[N]; // 結果存在sum裏  
  28. int vis[N];  
  29. void brc(complex *a,int l){//原來神犇的二進制平攤反轉置換太神看不懂,蒟蒻寫了一個O(n)的……   
  30.     memset(vis,0,sizeof(vis));//O(logn)的在後面   
  31.     for(int i=1;i<l-1;i++){  
  32.         int x=i,y=0;  
  33.         int m=(int)log2(l)+0.1;  
  34.         if(vis[x])continue;  
  35.         while(m--){  
  36.             y<<=1;  
  37.             y|=(x&1);  
  38.             x>>=1;  
  39.         }  
  40.         vis[i]=vis[y]=1;  
  41.         swap(a[i],a[y]);  
  42.     }     
  43. }  
  44. void fft(complex *y,int l,double on) // FFT O(nlogn)  
  45.                             // 其中on==1時爲DFT,on==-1爲IDFT  
  46. {  
  47.     register int h,i,j,k;  
  48.     complex u,t;   
  49.     brc(y,l); // 調用反轉置換  
  50.     for(h=2;h<=l;h<<=1) // 控制層數  
  51.     {  
  52.         // 初始化單位復根  
  53.         complex wn(cos(on*2*pi/h),sin(on*2*pi/h));  
  54.         for(j=0;j<l;j+=h) // 控制起始下標  
  55.         {  
  56.             complex w(1,0); // 初始化螺旋因子  
  57.             for(k=j;k<j+h/2;k++) // 配對  
  58.             {  
  59.                 u=y[k];  
  60.                 t=w*y[k+h/2];  
  61.                 y[k]=u+t;  
  62.                 y[k+h/2]=u-t;  
  63.                 w=w*wn; // 更新螺旋因子  
  64.             } // 據說上面的操作叫蝴蝶操作…  
  65.         }  
  66.     }  
  67.     if(on==-1)  for(i=0;i<l;i++) y[i].r/=l; // IDFT  
  68. }  
  69. /*  
  70. void fft2(complex *a,int s,int t){//蒟蒻自己寫的遞歸版FFT,不保證正確 ,代碼內部有未定義變量  
  71.     if((n>>t)==1)return;//s記錄起始,t記錄深度,調用時應從0開始  
  72.     fft(a,s,t+1); 
  73.     fft(a,s+(1<<t),t+1); 
  74.     for(int i=0;i<(n>>(t+1));i++){ 
  75.         p=(i<<(t+1))+s; 
  76.         wt=w[i<<t]*a[p+(1<<t)]; 
  77.         tt[i]=a[p]+wt; 
  78.         tt[i+(n>>(t+1))]=a[p]-wt; 
  79.     } 
  80.     for(i=0;i<(n>>t);i++)a[(i<<t)+s]=tt[i]; 
  81. }*/  
  82. int main(void)  
  83. {  
  84.     int l1,l2,l;  
  85.     register int i;  
  86.     while(scanf("%s%s",a,b)!=EOF)  
  87.     {  
  88.         l1=strlen(a);  
  89.         l2=strlen(b);  
  90.         l=1;  
  91.         while(l<l1*2 || l<l2*2)   l<<=1; // 將次數界變成2^n  
  92.                                         // 配合二分與反轉置換  
  93.         for(i=0;i<l1;i++) // 倒置存入  
  94.         {  
  95.             x1[i].r=a[l1-i-1]-'0';  
  96.             x1[i].i=0.0;  
  97.         }  
  98.         for(;i<l;i++)    x1[i].r=x1[i].i=0.0;  
  99.         // 將多餘次數界初始化爲0  
  100.         for(i=0;i<l2;i++)  
  101.         {  
  102.             x2[i].r=b[l2-i-1]-'0';  
  103.             x2[i].i=0.0;  
  104.         }  
  105.         for(;i<l;i++)    x2[i].r=x2[i].i=0.0;  
  106.         fft(x1,l,1); // DFT(a)  
  107.         fft(x2,l,1); // DFT(b)  
  108.         for(i=0;i<l;i++) x1[i]=x1[i]*x2[i]; // 點乘結果存入a  
  109.         fft(x1,l,-1); // IDFT(a*b)  
  110.         for(i=0;i<l;i++) sum[i]=x1[i].r+0.5; // 四捨五入  
  111.         for(i=0;i<l;i++) // 進位  
  112.         {  
  113.             sum[i+1]+=sum[i]/10;  
  114.             sum[i]%=10;  
  115.         }  
  116.         l=l1+l2-1;  
  117.         while(sum[l]<=0 && l>0)   l--; // 檢索最高位  
  118.         for(i=l;i>=0;i--)    putchar(sum[i]+'0'); // 倒序輸出  
  119.         putchar('\n');  
  120.     }  
  121.     return 0;  
  122. }  
  123. /*void brc(complex *y,int l) // 二進制平攤反轉置換 O(logn) 
  124. { 
  125.     register int i,j,k; 
  126.     for(i=1,j=l/2;i<l-1;i++) 
  127.     { 
  128.         if(i<j)  swap(y[i],y[j]); // 交換互爲下標反轉的元素 
  129.                                 // i<j保證只交換一次 
  130.         k=l/2; 
  131.         while(j>=k) // 由最高位檢索,遇1變0,遇0變1,跳出 
  132.         { 
  133.             j-=k; 
  134.             k>>=1; 
  135.         } 
  136.         if(j<k)  j+=k; 
  137.     } 
  138. }*/  

pyc神犇的寫法,bzoj3527,zjoi2014 力,無限YM

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. #include<cmath>  
  2. #include<cstdio>  
  3. #include<cstring>  
  4. #include<iostream>  
  5. #include<algorithm>  
  6. using namespace std;  
  7. const int maxn=1000010;  
  8. int n,N,L;  
  9. int rev[maxn];  
  10. int dig[maxn];  
  11. double p[maxn];  
  12. struct cp{  
  13.     double r,i;  
  14.     cp(double _r=0,double _i=0):  
  15.         r(_r),i(_i){}  
  16.     cp operator+(cp x){return cp(r+x.r,i+x.i);}  
  17.     cp operator-(cp x){return cp(r-x.r,i-x.i);}  
  18.     cp operator*(cp x){return cp(r*x.r-i*x.i,r*x.i+i*x.r);}  
  19. };  
  20. cp a[maxn],b[maxn],c[maxn],A[maxn],x,y;  
  21. void FFT(cp a[],int flag){  
  22.     for(int i=0;i<N;i++)A[i]=a[rev[i]];  
  23.     for(int i=0;i<N;i++)a[i]=A[i];  
  24.     for(int i=2;i<=N;i<<=1){  
  25.         cp wn(cos(2*M_PI/i),flag*sin(2*M_PI/i));  
  26.         for(int k=0;k<N;k+=i){  
  27.             cp w(1,0);  
  28.             for(int j=0;j<i/2;j++){  
  29.                 x=a[k+j];  
  30.                 y=w*a[k+j+i/2];  
  31.                 a[k+j]=x+y;  
  32.                 a[k+j+i/2]=x-y;  
  33.                 w=w*wn;    
  34.             }  
  35.         }  
  36.     }  
  37.     if(flag==-1)for(int i=0;i<N;i++)a[i].r/=N;  
  38. }  
  39. double anss[maxn];  
  40. int main(){  
  41.     scanf("%d",&n);  
  42.     for(int i=0;i<n;i++)scanf("%lf",&p[i]);  
  43.     for(L=0,N=1;N<n;N<<=1,L++);L++;N<<=1;  
  44.     for(int i=0;i<N;i++){  
  45.         int len=0;  
  46.         for(int t=i;t;t>>=1)dig[len++]=t&1;  
  47.         for(int j=0;j<L;j++)rev[i]=rev[i]*2+dig[j];  
  48.     }  
  49.     for(int i=0;i<n;i++)a[i]=cp(p[i],0);  
  50.     for(int i=1;i<n;i++)b[i]=cp(1.0/i/i,0);  
  51.     FFT(a,1);FFT(b,1);  
  52.     for(int i=0;i<N;i++)c[i]=a[i]*b[i];  
  53.     FFT(c,-1);  
  54.     for(int i=0;i<n;i++)anss[i]=c[i].r;  
  55.     memset(a,0,sizeof(a));  
  56.     memset(b,0,sizeof(b));  
  57.     for(int i=0;i<n;i++)a[i]=cp(p[n-i-1],0);  
  58.     for(int i=1;i<n;i++)b[i]=cp(1.0/i/i,0);  
  59.     FFT(a,1);FFT(b,1);  
  60.     for(int i=0;i<N;i++)c[i]=a[i]*b[i];  
  61.     FFT(c,-1);  
  62.     for(int i=0;i<n;i++)anss[i]-=c[n-i-1].r;  
  63.     for(int i=0;i<n;i++)  
  64.         printf("%.9f\n",anss[i]);  
  65.     return 0;  
  66. }  


重新過了一遍高精乘

[cpp] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. #include<cstdio>  
  2. #include<cmath>  
  3. #include<cstring>  
  4. #include<iostream>  
  5. #include<algorithm>  
  6. using namespace std;  
  7. const int maxn=1e6+10;  
  8. struct cp{  
  9.     double r,i;  
  10.     cp(double _r=0,double _i=0):  
  11.         r(_r),i(_i){}  
  12.     cp operator+(cp x){return cp(r+x.r,i+x.i);}  
  13.     cp operator-(cp x){return cp(r-x.r,i-x.i);}  
  14.     cp operator*(cp x){return cp(r*x.r-i*x.i,r*x.i+i*x.r);}  
  15. };  
  16. cp a[maxn],b[maxn],A[maxn],x,y,c[maxn];  
  17. char s1[maxn],s2[maxn];  
  18. int sum[maxn],a1[maxn],a2[maxn],dig[maxn];  
  19. int len1,len2,rev[maxn],N,L;  
  20. void FFT(cp a[],int flag){  
  21.     for(int i=0;i<N;i++)A[i]=a[rev[i]];  
  22.     for(int i=0;i<N;i++)a[i]=A[i];  
  23.     for(int i=2;i<=N;i<<=1){  
  24.         cp wn(cos(2*M_PI/i),flag*sin(2*M_PI/i));  
  25.         for(int k=0;k<N;k+=i){  
  26.             cp w(1,0);  
  27.             for(int j=k;j<k+i/2;j++){  
  28.                 x=a[j];  
  29.                 y=a[j+i/2]*w;  
  30.                 a[j]=x+y;  
  31.                 a[j+i/2]=x-y;  
  32.                 w=w*wn;  
  33.             }  
  34.         }  
  35.     }  
  36.     if(flag==-1)for(int i=0;i<N;i++)a[i].r/=N;  
  37. }  
  38. int main(){  
  39.     scanf("%s%s",s1,s2);  
  40.     len1=strlen(s1);  
  41.     len2=strlen(s2);  
  42.     for(N=1,L=0;N<max(len1,len2);N<<=1,L++);N<<=1;L++;  
  43.     for(int i=0;i<N;i++){  
  44.         int len=0;  
  45.         for(int t=i;t;t>>=1)dig[len++]=t&1;  
  46.         for(int j=0;j<L;j++)rev[i]=(rev[i]<<1)|dig[j];  
  47.     }  
  48.     for(int i=0;i<len1;i++)a1[len1-i-1]=s1[i]-'0';  
  49.     for(int i=0;i<len2;i++)a2[len2-i-1]=s2[i]-'0';  
  50.     for(int i=0;i<N;i++)a[i]=cp(a1[i]);  
  51.     for(int i=0;i<N;i++)b[i]=cp(a2[i]);  
  52.     FFT(a,1);FFT(b,1);  
  53.     for(int i=0;i<N;i++)c[i]=a[i]*b[i];  
  54.     FFT(c,-1);  
  55.     for(int i=0;i<N;i++)sum[i]=c[i].r+0.5;  
  56.     for(int i=0;i<N;i++){  
  57.         sum[i+1]+=sum[i]/10;  
  58.         sum[i]%=10;  
  59.     }  
  60.     int l=len1+len2-1;  
  61.     while(sum[l]==0&&l>0)l--;  
  62.     for(int i=l;i>=0;i--)  
  63.     putchar(sum[i]+'0');  
  64.     putchar('\n');  
  65.     return 0;  
  66. }  
查看原文



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章