快速傅里葉變換(FFT)

FFT,是用來在O(nlogn)時間複雜度內解決關於單一元素的多項式*多項式問題的算法。

衆所周知,一個關於x的n項式可以表示爲a[0]+a[1]*x+a[2]*x^2+...+a[n-1]*x^(n-1),這就是係數表示法。

我們考慮另一種表示方法,對於一個上述形式的n項式,我們只要知道n組不同的x,和將這n組x代入n項式後得到的值(我們設其爲f(x)),就可以唯一確定這個n項式。用這樣的n組(x,f(x))表示一個n項式就是點值表示法。

當兩個多項式相乘時,如果是用係數表示法,那麼時間複雜度不可避免是O(n^2),但如果這兩個多項式是用點值表示法表示的,且所選的x的集合都相同,那麼我們的新多項式用點值表示法即爲n組(x,f1(x)*f2(x))。

但是!日常生活中我們正常是不會用點值表示法來表示多項式的,所以FFT就是用來將一個多項式的係數表示法轉化爲點值表示法的算法。而IFFT(快速傅里葉逆變化)就是用來將多項式從點值表示法轉化爲係數表示法的算法。

關於這n組不同的x到底怎麼選,快速傅里葉變換是有規定的選值的。

對於一個n項式,令複數w(n,k)=cos(2*pi*k/n)+sin(2*pi*k/n)i。(其中0≤k≤n-1,pi爲3.1415926535....,2*pi=360°)。xk=w(n,k)共n組。

這是從代數意義來看的(其實可以直接背),從幾何意義來看,w(n,k)都在複數平面以原點爲圓心半徑爲1的圓上。且是將這個圓n等分,w(n,0)=1+0i, k以逆時針遞增取點。 然後根據三角函數亂搞一波就可以知道上述代數式。

但我們知道一個x,求f(x)還是要n的時間。怎麼辦呢?

copy自https://blog.csdn.net/enjoy_pascal/article/details/81478582#commentBox 多謝WD大佬!


好了,FFT搞完了。

接下來看IFFT,有這樣一個結論:一個多項式在分治的過程中乘上單位根的共軛複數,分治完的每一項除以n nn即爲原多項式的每一項係數。意思就是說這個可以一樣用FFT的函數做。具體看模板吧。不太會描述的說。

值得注意的是,當給一個n項多項式,一個m項多項式,應令N=n+m-1,並將N調整爲一個更大的2次冪數。

然後fft(a,N,1); fft(b,N,1);

ai*=b[i]

fft(a,N,-1) 

模板(我只會遞歸的:

#include<cstdio>
#include<algorithm>
using namespace std;
#include<complex>
#define cp complex<double> //這是一個複數類型
#include<cmath> 
#define pi 3.1415926535
cp a[3000010],b[3000010];
void fft(cp *a,int n,int bz) //這裏的bz表示現在是點轉系還是系轉點
{
    if (n==1) return;
    int mid=n/2;
    cp b[n+10];
    for (int i=0;i<=mid-1;i++) b[i]=a[i*2],b[i+mid]=a[i*2+1];//這裏a,b還是係數
    for (int i=0;i<=n-1;i++) a[i]=b[i]; //將b分爲基數和偶數部分
    fft(a,n/2,bz); fft(a+mid,n/2,bz);//遞歸求出A1,A2
    for (int i=0;i<=mid-1;i++)
    {
        cp t(cos(2*pi*i/n),bz*sin(2*pi*i/n)); //t=w(n,i)
        b[i]=a[i]+t*a[i+mid]; b[i+mid]=a[i]-t*a[i+mid];
    }
    for (int i=0;i<=n-1;i++) a[i]=b[i];//a已經變成點值了
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    int t=n+m+1;
    for (int i=0;i<=n;i++) scanf("%lf",&a[i].real());
    for (int i=0;i<=m;i++) scanf("%lf",&b[i].real());
    for (n=1;n<t;n*=2);//把n弄成2的冪
    fft(a,n,1); fft(b,n,1);//系轉點
    for (int i=0;i<=n-1;i++) a[i]=a[i]*b[i];//點值相乘
    fft(a,n,-1);//點轉系
    for (int i=0;i<=t-1;i++) printf("%d ",int(a[i].real()/n+0.5));//要/n後四捨五入纔是真正的係數
    return 0;
}

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