坑逼FFT的入門

請各位看官移至 此處

簡單的預備知識

係數表達

用多項式每一項的係數表示多項式

點值表達

n 個橫座標各不相同的點(x,y) 來表示一個次數界爲n 的多項式

求值

係數表達=> 點值表達
O(n2) 霍納法則

插值

點值表達=> 係數表達
O(n2) 拉格朗日公式

係數表達式求多項式加,乘法

A={a0,a1,,an1}
B={a0,a1,,an1}

C=A+B={a0+a0,a1+a1,an1+an1}
C=AB,cx=i=0xaiaxi

點值表達式求多項式加,乘法

A={(x0,y0),(x1,y1),,(xn1,yn1)}
B={(x0,y0),(x1,y1),,(xn1,yn1)}

C=A+B={(x0,y0+y0),(x1,y1+y1),,(xn1,yn1+yn1)}
C=AB={(x0,y0y0),(x1,y1y1),,(xn1,yn1yn1)}


顯然的,用係數表達式算多項式乘法簡直是龜速O(n2)
而使用點值表達式算多項式乘法則快很多(O(n))

可以看出主要若使用點值表達式,主要複雜度在求值和插值(均爲O(n2) )
顯然如果求值插值不能優化的話其複雜度甚至是不如係數乘法的(常數大)
所以我們可以利用一些特殊的方式來加速

引入單位複數根

n 次單位複數根是滿足wn=1 的複數w ,n 次單位複數根恰有n
對於k=0,1,,n1 ,這些根是e2πikn .(eπi=1,e2πi=1 )
用複數的指數形式的定義eui=cos(u)+isin(u)
可以建立一個以實數爲x 軸,以虛數爲y 軸的座標系
然後這些單位複數根在座標軸上正好是半徑爲1的圓上的點
我們將e2πi1n 稱爲主n 次單位根,利用它,其他的單位複數根都是其冪次
然後這些單位複數根的乘法就相當於角度的轉換
單位複數根之間滿足乘法羣的性質(modn)

消去引理: wdndk=wnk
* 證明:wdndk=(e2πi1dn)dk=(e2πi1n)k=wnk

推論: w2nn=1

折半引理:n 爲偶數,則n 次單位複數根的平方的集合就是n2 次單位複數根的集合,特別的,每個n2 次單位複數根出現兩次
* 證明:(wnk)2=wn2k,k[0,n1] [消去引理]

正式開始

對於n 次多項式,我們希望取得其在wn0,wn1,,wnn1 處的值
相當於求這樣一個矩陣:

[A0A1...An1] =[wn0wn0wn0wn0wn1wnn1........wn0wnn1wn(n1)(n1)][a0a1...an1]

其中Ai 爲取到wni 時的點值,ai 爲第i 位的係數
A(i)=j=0n1wnijaj

現定義:
A[0](x)=a0+a2x++an2xn22
A[1](x)=a1+a3x++an1xn22
易得:
A(x)=A[0](x2)+A[1](x2)

因爲當x=wnkx=wnk+n2 時,兩數平方相等
所以說我們的取值集合就縮小了一半
這樣一直遞歸下去
每層有2d 個塊,每個塊有n2d 個取值,所以一共只需計算n 次,共log(n) 層,總時間複雜度爲O(nlog(n))
非常的nice
這個過程也叫做DFT

得到點值表達式以後我們就只需要O(n) 的點值乘法,這裏不再贅述

那麼接下來的任務就是插值了,也就是逆DFT
易得這個矩陣:

[C0C1...Cn1] =[wn0wn0wn0wn0wn1wnn1........wn0wnn1wn(n1)(n1)][c0c1...cn1]

其中Ci 爲取到wni 時的點值,ci 爲第i 位的係數
所以c=CV1
V1V= 單位矩陣
所以V1 中位於第i 行第j 列(均從0開始標號)的元素爲wnijn
所以
ci=1nj=0n1Cj(wni)j

是不是和原來的公式很像?
A(i)=j=0n1ajwnij

只不過w 的係數變成了負的
多了一個1n 而已
發現也可以DFT 解決
這樣就做完啦!

但是具體代碼中還是有丶東西
看註釋吧!

代碼如下:

#include <bits/stdc++.h>
using namespace std;
const int N =100010;
const double pi=acos(-1);
struct Complex {
    double x;
    double y;
    Complex() {};
    Complex(double _x,double _y) { x=_x;y=_y; }
    Complex operator + (const Complex o) { return Complex(x+o.x,y+o.y); }
    Complex operator - (const Complex o) { return Complex(x-o.x,y-o.y); }
    Complex operator * (const Complex o) { return Complex(x*o.x-y*o.y,x*o.y+y*o.x); }
}a[N<<2],b[N<<2];
int r[N<<2];
int n,m,nn;
void fft(Complex *now,int f) {
    for(int i=0;i<n;++i)//按塊將其分到一起
        if(i<r[i]) swap(now[i],now[r[i]]);
    for(int i=1;i<n;i<<=1) {//枚舉合併的塊的大小
        Complex wn(cos(pi/i),f*sin(pi/i));//單位元,若進行逆DFT變換,則反向旋轉
        for(int j=0;j<n;j+=(i<<1)) {//枚舉頭
            Complex w(1,0);
            for(int k=0;k<i;++k,w=w*wn) {//合併,蝴蝶變換
                Complex x=now[j+k],y=w*now[j+k+i];
                now[j+k]=x+y;
                now[j+k+i]=x-y;
            }
        }
    }
    if(f==-1)
        for(int i=0;i<n;++i)
            now[i].x/=n;
}
int main() {
    scanf("%d%d",&n,&m);
    for(int i=0;i<=n;++i) scanf("%lf",&a[i].x);
    for(int i=0;i<=m;++i) scanf("%lf",&b[i].x);
    m+=n;
    for(n=1;n<=m;n<<=1) nn++;
    for(int i=0;i<n;++i) {r[i]=(r[i>>1]>>1)|((i&1)<<(nn-1));}
    fft(a,1);fft(b,1);
    for(int i=0;i<=n;++i) { a[i]=a[i]*b[i]; }
    fft(a,-1);//逆DFT
    for(int i=0;i<=m;++i)
        printf("%d ",(int)(a[i].x+0.5))//防止精度出鍋;
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章