多項式
假設有次多項式:
如何表示這個多項式.
首先想到的是用所有係數表示,即:
我們將這樣的表示方法爲係數表示法.
但是,係數表示法在表示多項式相加和相乘很不方便.
譬如又有次表達式:
那麼將兩式子相乘的時間複雜度是的.
如果=,這種算法就不是那麼優越.
所幸,我們還有另一種表示方法——點值表示法.
設想,如果我們用足夠多的值對(x,y) 表示這個函數:
表達式的係數是可以確定的.
更美妙的是,在點值表達式下,多項式加法和乘法是可以在實現的!
比如,有點對
那麼相乘得
妙啊
轉換
但是,我們在實際生活中並不經常使用點值表示法,而是經常使用係數表示法.
於是,便有了兩者之間的轉換.
係數表示法和點值表示法可以用如下矩陣乘法轉換:
這種變換,叫作離散傅里葉變換(DFT).
反之,由點值表示法退回係數表示法,叫作離散傅里葉逆變換(IDFT).
花了好久點個讚唄
但是發現,這樣轉換時間複雜度還是的!
所以,就有了快速傅里葉變換(FFT).
快速傅里葉變換
又稱(Fast Fourier Transform)(FFT),詳情見Mr.Du.
快速傅里葉變換是離散傅里葉變換的快速版本.這還用說嗎
它可以以優秀的解決這個問題.
舉個例子:
我們代入這個特殊值,那麼可以得到.
我們代入、也可以得到特殊值.
但是,這樣的特殊值總是有限的.
所以,我們將眼光轉向別處——複數域.
比如說有一個複數座標系:
這個圓叫作單位圓沒錯我們見過,上面的點都可以乘方到1.
如果想要乘方次,那麼這些值就是這樣分佈的:
這是的情況.最好畫了不是嗎
//下面所有都是的冪,注意!
我們稱這樣的數列爲.(當然是乘方n次後爲1的 )
並稱這些爲 的次單位根 .
鋪墊
由神奇而美妙的歐拉公式:
還有複數的極角表達式:
以及複數運算滿足交換律和結合律.
我們就可以以這些爲鋪墊,進行接下來的研究了 ?
定理
下面的定理不再證明,可以結合奆老博客和Mr.Du證明:
//提示:歐拉定理和複平面(上圖)是個好東西.
對於序列,(很實用)
算法構建
經過這麼一波操作之後,好像什麼都沒做…
因爲直到現在,我們還是沒有在這個數據上構造優化算法.
構造那麼神奇的數據,不實現又有何用
要不我們先將一個代入試試.
比如我們設定
看上去沒什麼變化…
但是我把它的奇數指數和偶數指數分開,分別寫成:
和
//是的冪.
所以我們震驚的發現,竟然有這樣的表達式!
哇我真棒
但是,這還是沒有用…
在仔細研究爲什麼需要使用複數.
因爲,!
所以,我們取得:
整理一下得():
出現了!FFT!
IFFT
看似好像不那麼容易.
但是我們已經會FFT了!
在分支向下的時候,保存每一項的係數,
具體怎麼保存呢?
在轉換的時候,我們乘的.
轉換回來的時候,就乘上它的共軛複數 What?你不會?
於是,我們就很快活
遞歸版FFT&IFFT
題目傳送門
//爲什麼叫BF很快揭曉
先隨便打個真·暴力上去…
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
long long a[N],b[N],c[N];
int n,m;
int main()
{
//True BF
//O(mn)...
scanf("%d%d",&m,&n);
for (int i=0;i<=m;i++) scanf("%d",&a[i]);
for (int i=0;i<=n;i++) scanf("%d",&b[i]);
for (int i=0;i<=m;i++)
for (int j=0;j<=n;j++)
c[i+j]+=a[i]*b[j];
for (int i=0;i<=n+m;i++)
printf("%d ",c[i]);
return 0;
}
Result:
//哇竟然能有55分!
而上面的諸多公式告訴我們,FFT可以完美解決這好像就是一道FFT模板題…
根據我們的FFT算法,很容易想到遞歸實現FFT:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const double pi=acos(-1.0);
const int N=5e6+5;
struct complex
{
double x,y;
complex(double _x=0,double _y=0) {x=_x,y=_y;}
}a[N],b[N];
complex operator + (complex a,complex b) {return complex(a.x+b.x,a.y+b.y);}
complex operator - (complex a,complex b) {return complex(a.x-b.x,a.y-b.y);}
complex operator * (complex a,complex b) {return complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}
void FFT(int n,complex *a,int inv)
{
if (n==1) return;
int k=n>>1;
complex a1[k],a2[k];
for (int i=0;i<n;i+=2) a1[i>>1]=a[i],a2[i>>1]=a[i+1];//A1和A2
FFT(k,a1,inv);//
FFT(k,a2,inv);//分治
complex Wn=complex(cos(2.*pi/n),inv*sin(2.*pi/n)),w=complex(1,0);//omegaN和單位元
for (int i=0;i<k;i++,w=w*Wn)
{
a[i]=a1[i]+w*a2[i];
a[i+k]=a1[i]-w*a2[i];//一次推兩個
}
}
int main()
{
int n,m;
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);
int p=1;
while (p<=n+m) p<<=1;
FFT(p,a,1),FFT(p,b,1);//係數轉點值
for (int i=0;i<=p;i++) a[i]=a[i]*b[i];
FFT(p,a,-1);//點值轉系數
for (int i=0;i<=n+m;i++) printf("%d ",(int)(abs)(a[i].x/p+0.5));
return 0;
}
然而我竟然AC了!這是怎麼回事?
Result:
這還怎麼提出迭代版FFT!別提了
迭代版FFT&IFFT
接下來,我們將它優化一下:
這裏是經過FFT分治後的序列
初始序列(id) | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
1次操作 | 0 | 2 | 4 | 6 | 1 | 3 | 5 | 7 |
2次操作 | 0 | 4 | 2 | 6 | 1 | 5 | 3 | 7 |
2進制編碼 | 000 | 100 | 010 | 110 | 001 | 101 | 011 | 111 |
2進制逆序編碼 | 000 | 001 | 010 | 011 | 100 | 101 | 110 | 111 |
我真聰明
可以發現,最後的編碼是按逆序升序排列的.
換句話說:每個下標位置處理後的位置是它二進制逆序後的位置!
用這個短小精悍的代碼就可以解決一切問題:
//名叫Rader算法
int p=1,L=0;
while (p<=n+m) p<<=1,L++;
for (int i=0;i<p;i++)
order[i]=(order[i>>1]>>1)|((i&1)<<(L-1));
//(i&1)<<(L-1) 是爲了考慮後面的1<<(L-1)個數
真有趣 ?
接下來,我們就更快活開心了 ?
蝴蝶效應
屬於小優化的範疇.
主要是因爲複數乘法過於緩慢,所以可以預先處理好.
而已qwq.不要想到龍捲風那裏去了
Code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=5e6+5;
const double pi=acos(-1.);
struct complex
{
double x,y;
complex(double _x=0,double _y=0) {x=_x,y=_y;}
}a[N],b[N];
complex operator + (complex a,complex b) {return complex(a.x+b.x,a.y+b.y);}
complex operator - (complex a,complex b) {return complex(a.x-b.x,a.y-b.y);}
complex operator * (complex a,complex b) {return complex(a.x*b.x-a.y*b.y,a.x*b.y+b.x*a.y);}
int order[N];
int p=1,L;
void FFT(complex *a,int inv)
{
for (int i=0;i<p;i++)
if (i<order[i]) swap(a[i],a[order[i]]);
for (int l=1;l<p;l<<=1)
{
complex Wn(cos(pi/l),inv*sin(pi/l));
for (int R=l<<1,j=0;j<p;j+=R)
{
complex w(1,0);
for (int k=0;k<l;k++,w=w*Wn)
{
complex x=a[j+k],y=w*a[j+l+k];//B
a[j+k]=x+y;
a[j+l+k]=x-y;
}
}
}
}
int main()
{
int n,m;
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);
while (p<=n+m) p<<=1,L++;
for (int i=0;i<p;i++)
order[i]=(order[i>>1]>>1)|((i&1)<<(L-1));
FFT(a,1),FFT(b,1);
for (int i=0;i<=p;i++) a[i]=a[i]*b[i];
FFT(a,-1);
for (int i=0;i<=n+m;i++)
printf("%d ",(int)(a[i].x/p+0.5));
return 0;
}
Result:
感謝奆老關注 qwq ?
後記
FFT,又稱Fast-Fast-TLE…
這裏爲什麼不直接從這個庫裏調用?
因爲…太慢了…