前置知識
兩種多項式表示法:
係數表示法: ,其中 就是係數。
點值表示法:代入 個不同的 得到對應的 記爲 ,這 個點 唯一確定了 。
由係數表示法變爲點值表示法稱爲求值,點值表示法變爲係數表示法稱爲插值。
點值表示法好處:快速求 的點值表達式——只需要將相同 的 乘起來。
單位複數根:
用 個模爲 的複數 等分複平面,令幅角最小的是 ( 次單位複數根),那麼其他 個就是 (兩個複數相乘,模長相乘,幅角相加),畫圖yy一下可以發現 。
因爲是 等分,所以 ,由此可見 。
快速傅里葉變換
因爲點值表示法可以快速進行多項式乘法,所以我們想將係數表示法轉換成點值表示法,再進行多項式相乘,最後將點值表示法轉換成係數表示法。兩個過程分別爲離散傅里葉變換和傅里葉逆變換。
ps:接下來都默認 是 (如果不夠那麼高位補 即可)。
離散傅里葉變換:
如果將 的係數奇偶拆分,我們可以得到:
因爲要代入 個不同的 ,我們會聯想到 次單位複數根,又因爲如果 是偶數,則 ,也就是說 的取值只有 種。於是我們驚奇的發現只要代入 個 次單位複數根, 和 就變成了與 同樣的問題(這就是爲什麼 要是 )!
所以可以用 的複雜度來愉快的離散傅里葉變換。
傅里葉逆變換:
只需要用 再做一次離散傅里葉變換,證明可以參考Menci大佬的博客QAQ,我太弱了就只記了結論:
實現
離散傅里葉變換的遞歸寫法很顯然,但是常數巨大啊。實際上有迭代寫法,我們觀察每個 遞歸到最後的位置:
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;
}