FFT

前置知識

兩種多項式表示法:

係數表示法:A(x)=i=0n1aixi ,其中 {an1} 就是係數。

點值表示法:代入 n 個不同的 x 得到對應的 A(x) 記爲 (x,y) ,這 n 個點 (x0,y0),(x1,y1)(xn1,yn1) 唯一確定了 A(x)

由係數表示法變爲點值表示法稱爲求值,點值表示法變爲係數表示法稱爲插值。

點值表示法好處:快速求 A(x)×B(x) 的點值表達式——只需要將相同 xy 乘起來。

單位複數根:

n 個模爲 1 的複數 n 等分複平面,令幅角最小的是 ωn ( n 次單位複數根),那麼其他 n1 個就是 ωn2,ωn3ωnn (兩個複數相乘,模長相乘,幅角相加),畫圖yy一下可以發現 ωn0=ωnn=1

因爲是 n 等分,所以 ωnk=(cos2kπn,sin2kπn) ,由此可見 ω2n2k=ωnk,ωnk+n2=ωnk

快速傅里葉變換

因爲點值表示法可以快速進行多項式乘法,所以我們想將係數表示法轉換成點值表示法,再進行多項式相乘,最後將點值表示法轉換成係數表示法。兩個過程分別爲離散傅里葉變換和傅里葉逆變換。

ps:接下來都默認 n2k (如果不夠那麼高位補 0 即可)。

離散傅里葉變換:

如果將 A(x) 的係數奇偶拆分,我們可以得到:

A0(x)=a0+a2x+a4x2++an2xn21A1(x)=a1+a3x+a5x2++an1xn21A(x)=A0(x2)+xA1(x2)

因爲要代入 n 個不同的 x ,我們會聯想到 n 次單位複數根,又因爲如果 n 是偶數,則 (ωnk)2=ωn2k ,也就是說 (ωnk)2 的取值只有 n2 種。於是我們驚奇的發現只要代入 nn 次單位複數根, A0(x2)A1(x2) 就變成了與 A(x) 同樣的問題(這就是爲什麼 n 要是 2k )!

所以可以用 O(nlog2n) 的複雜度來愉快的離散傅里葉變換。

傅里葉逆變換:

只需要用 ωnk 再做一次離散傅里葉變換,證明可以參考Menci大佬的博客QAQ,我太弱了就只記了結論:

ak=1ni=0n1ai(ωnk)i

實現

離散傅里葉變換的遞歸寫法很顯然,但是常數巨大啊。實際上有迭代寫法,我們觀察每個 i 遞歸到最後的位置:

0 1 2 3 4 5 6 7
0 2 4 6|1 3 5 7
0 4|2 6|1 5|3 7
0|4|2|6|1|5|3|7
二進制:
 0   1   2   3   4   5   6   7
000 001 010 011 100 101 110 111
000 100 010 110 001 101 011 111
 0   4   2   6   1   5   3   7

驚奇的發現竟然是二進制反轉QAQ,這樣我們就可以從底向上迭代處理了。

模板

UOJ34,爛大街的FFT模板題。

#include<cstdio>
#include<cctype>
#include<cmath>
#include<algorithm>
#define fr first
#define sc second
#define mp make_pair
using namespace std;
typedef long double DB;typedef pair<DB,DB> C;
const int maxn=262144;const DB pi=acos(-1);

int n,m,R[maxn+5];C a[maxn+5],b[maxn+5];

#define Eoln(x) ((x)==10||(x)==13||(x)==EOF)
inline char readc(){
    static char buf[100000],*l=buf,*r=buf;
    if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
    if (l==r) return EOF;return *l++;
}
inline int readi(int &x){
    int tot=0,f=1;char ch=readc(),lst='+';
    while (!isdigit(ch)) {if (ch==EOF) return EOF;lst=ch;ch=readc();}
    if (lst=='-') f=-f;
    while (isdigit(ch)) tot=(tot<<3)+(tot<<1)+ch-48,ch=readc();
    return x=tot*f,Eoln(ch);
}
inline int Rev(int x,int len){
    static int buf[31];for (int i=0;i<len;i++) buf[i]=x&1,x>>=1;
    for (int i=0;i<len;i++) x=x<<1|buf[i];return x;
}
C operator + (const C &a,const C &b) {return mp(a.fr+b.fr,a.sc+b.sc);}
C operator - (const C &a,const C &b) {return mp(a.fr-b.fr,a.sc-b.sc);}
C operator * (const C &a,const C &b) {return mp(a.fr*b.fr-a.sc*b.sc,a.fr*b.sc+a.sc*b.fr);}
inline void FFT(C *a,int n,int f){
    for (int i=0;i<n;i++) if (i<R[i]) swap(a[i],a[R[i]]);
    for (int k=1;k<n;k<<=1){
        C w=mp(1,0),wn=mp(cos(pi/k),sin(f*pi/k)),x,y;
        for (int i=0;i<n;i+=(k<<1),w=mp(1,0))
            for (int j=0;j<k;j++,w=w*wn)
                x=a[i+j],y=w*a[i+j+k],a[i+j]=x+y,a[i+j+k]=x-y;
    }
}
int main(){
    freopen("FFT.in","r",stdin);
    freopen("FFT.out","w",stdout);
    readi(n);readi(m);
    for (int i=0,x;i<=n;i++) readi(x),a[i]=mp(x,0);
    for (int i=0,x;i<=m;i++) readi(x),b[i]=mp(x,0);
    m+=n;int len=0;for (n=1;n<=m;n<<=1) len++;for (int i=0;i<n;i++) R[i]=Rev(i,len);
    FFT(a,n,1);FFT(b,n,1);for (int i=0;i<n;i++) a[i]=a[i]*b[i];
    FFT(a,n,-1);for (int i=0;i<=m;i++) printf("%d ",(int)(a[i].fr/n+0.1));
    return 0;
}
發佈了340 篇原創文章 · 獲贊 124 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章