FFT,快速傅里葉變換,其實也沒那麼神祕,就是一種變換方式罷了。在音頻視頻傳送中有很多應用,此處不贅述,只談談其在算法競賽中的用途。
FFT,一般用來快速乘,當然還有些其他應用。
比如給你a,b,兩個數,他們很大,超過了10w位,n^2的乘法就顯得太慢。
而FFT就是能nlogn時間解決此類問題的算法。
對於一個多項式A(x) = a0+a1*x+a2*x^2+...+an-1*x^(n-1)
這種是這個多項式的一個表示方法,用係數和權來表示,這樣乘起來是n^2的,我們需要轉換下表示法,及用點值表示法。什麼是點值表示法呢?感覺上來說,你可以把A(x)當成一條函數曲線在直角座標系中畫出來,上面選取n個不同的點(n次就選n個)這n個點就能確定這條直線了,那麼就用這n個點的集合來表示A(x),這叫點值表示法。
點選取哪些呢?
選取n次單位復根作爲來求點值是比較巧妙的做法。
n次單位復根是指 w^n = 1,w是複數,這樣的根恰好有n個,爲e^(2*PI*i*k/n) k(0,1,...,n-1);
可以用歐拉公式e^(i*u) = cos(u) + i*sin(u)
將其展開,
然後多項式的卷積,通過變換到點值表達式,就成了複數的對應積在求和,複雜度爲O(n)
如何快速的從係數表示法轉化到點值表示法呢?
那就是FFT了。
至於FFT和n次單位復根詳解,看這篇博客吧,我懶得寫公式了http://blog.csdn.net/acdreamers/article/details/39005227
所以多項式乘法就是,先轉化爲點值表達式,然後求積,再反轉爲係數表達式,然後隨你處理。
核心代碼如下:
struct Virt {
double r, i;
Virt(double _r = 0, double _i = 0) : r(_r), i(_i) {}
Virt operator+ (Virt& rhs) {
return Virt(r + rhs.r, i + rhs.i);
}
Virt operator- (Virt& rhs) {
return Virt(r - rhs.r, i - rhs.i);
}
Virt operator* (Virt& rhs) {
return Virt(r * rhs.r - i * rhs.i, r * rhs.i + i * rhs.r);
}
};
void Rader(Virt F[], int len) {
int j = len >> 1;
for(int i = 1; i < len - 1; i++) {
if(i < j) swap(F[i], F[j]);
int k = len >> 1;
while(j >= k) {
j -= k;
k >>= 1;
}
if(j < k) j += k;
}
}
void FFT(Virt F[], int len, int on) {
Rader(F, len);
for(int h = 2; h <= len; h <<= 1) {
Virt wn(cos(-on*2*PI/h), sin(-on*2*PI/h));
for(int j = 0; j < len; j += h) {
Virt w(1, 0);
for(int k = j; k < j + h / 2; k++) {
Virt u = F[k];
Virt t = w * F[k + h / 2];
F[k] = u + t;
F[k + h / 2] = u - t;
w = w * wn;
}
}
}
if(on == -1)
for(int i = 0; i < len; i++)
F[i].r /= len;
}
void Conv(Virt a[], Virt b[], int len) {
FFT(a, len, 1);
FFT(b, len, 1);
for(int i = 0; i < len; i++)
a[i] = a[i] * b[i];
FFT(a, len, -1);
}
Virt va[maxn], vb[maxn];
int len;
最後結果存在result裏面