O(logN) 计算两个多项式的卷积 .
单位根为在单位圆上的点.横坐标为实部,纵坐标为虚部的复数.
如图, 图中落在 x 正半轴的那个红点称为 wn0, 被称为 n 次单位根, 其余的点从 wn0 逆时针顺序分别称为 wn1,wn2...wn7 .
从图中可以 感性 得到 单位根 的 2 个性质 ↓
-
性质1: w2n2k=wnk
-
性质2: wnk+2n=−wnk
下面给出 理性 证明, 首先要知道
欧拉公式:wnk=cos(k∗n2π)+i∗sin(k∗n2π)
其中 i 为 −1 .
- 性质1: w2n2k=wnk
证:
w2n2k=cos(2k∗2n2π)+i∗sin(2k∗2n2π)=wnk
- 性质2: wnk+2n=−wnk
证:
wnk+2n=cos[(k+2n)∗n2π]+i∗sin[(k+2n)∗n2π)]=−wnk
∵一个n次多项式可以被n+1个点唯一确定.
∴将wn0⋯wnn−1代入可以确定该多项式.
这里默认 n 为 2 的整数次幂, 所以 n−1 为奇数 .
- 对于上方多项式 A(x), 将其 系数 按奇偶分开, 得 ↓
A(x)=(a0+a2∗x2+⋯+an−2∗xn−2)+(a1∗x+a3∗x3+⋯+an−1∗xn−1)
设
A1(x)=a0+a2x+a4x2+⋯+an−2x2n−1 A2(x)=a1+a3x+a5x2+⋯+an−1x2n−1
则
A(x)=A1(x2)+x∗A2(x2)
代入 wnk (k<2n) 得
A(wnk)=A1(wn2k)+wnkA2(wn2k)=A1(w2nk)+wnkA2(w2nk)
代入 wnk+2n 得
A(wnk+2n) =A1(wn2k+n)+wnk+2nA2(wn2k+n) =A1(wn2k∗wnn)−wnkA2(wn2kwnn) =A1(wn2k)−wnkA2(wn2k) =A1(w2nk)−wnkA2(w2nk)
结论:
-
A(wnk)=A1(w2nk)+wnkA2(w2nk)
-
A(wnk+2n)=A1(w2nk)−wnkA2(w2nk)
可以观察到第二个式子与第一个式子的唯一区别就是正负号 .
根据上方式子分治处理出每个子项, 再向上合并, 时间复杂度 O(NlogN) .
总结:
FFT是先将多项式转换为 <stron>, 然后进行分治处理, 再转变为多项式的 O(nlogn) 算法 .</stron>
推导过程:
- 把系数按奇偶分开
- 代入 单位根 .
- 得到 “分治向上递推式” .
借用 这位大佬 的一个图,
所谓蝴蝶变换, 就是将原序列进行二进制翻转的变换 .
rev[i]=(rev[i>>1]>>1)∣((i&1)<<bit_num−1)
详解点击 这里 .
下面是 模板 代码 .
#include<bits/stdc++.h>
#define reg register
const int maxn = 1e6 + 5;
const double Pi = acos(-1);
struct complex{
double x, y;
complex(double x = 0, double y = 0):x(x), y(y) {}
} A[maxn<<2], B[maxn<<2];
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); }
int N, M;
int rev[maxn<<2];
void FFT(complex *F, short Opt){
for(int i = 0; i < N; i ++)
if(i < rev[i]) std::swap(F[i], F[rev[i]]);
for(int p = 2; p <= N; p <<= 1){
int len = p >> 1;
complex tmp(cos(Pi/len), Opt*sin(Pi/len));
for(int i = 0; i < N; i += p){
complex Buf(1, 0);
for(reg int k = i; k < i+len; k ++){
complex Temp = Buf * F[k+len];
F[k+len] = F[k] - Temp;
F[k] = F[k] + Temp;
Buf = Buf * tmp;
}
}
}
}
int main(){
N = read(), M = read();
for(int i = 0; i <= N; i ++) A[i].x = read();
for(int i = 0; i <= M; i ++) B[i].x = read();
int bit_num = 0;
for(M += N, N = 1; N <= M; N <<= 1) bit_num ++;
for(int i = 0; i < N; i ++) rev[i] = (rev[i>>1]>>1)|((i&1)?N>>1:0);
// for(int i = 0; i < N; i ++) rev[i] = (rev[i>>1]>>1) | ((i&1) << bit_num-1);
FFT(A, 1), FFT(B, 1);
for(int i = 0; i < N; i ++) B[i] = A[i] * B[i];
FFT(B, -1);
for(reg int i = 0; i <= M; i ++) printf("%.0lf ", fabs(B[i].x/N));
return 0;
}
- 将单位根 wn 替换为原根 glenmod−1
- 最后乘的是 T_len 的逆元 .
其中 g 在 模数 998244353下取 3 .
void FFT(complex *F, int opt){
for(reg int i = 0; i < FFT_Len; i ++)
if(i < rev[i]) std::swap(F[i], F[rev[i]]);
for(reg int p = 2; p <= FFT_Len; p <<= 1){
int half = p >> 1;
complex t = complex(cos(Pi/half), opt*sin(Pi/half));
for(reg int i = 0; i < FFT_Len; i += p){
complex buf = complex(1, 0);
for(reg int k = i; k < i+half; k ++){
complex Tmp = buf * F[k + half];
F[k + half] = F[k] - Tmp;
F[k] = F[k] + Tmp;
buf = buf * t;
}
}
}
}
void NTT(int *f, int opt){
for(reg int i = 0; i < T_len; i ++)
if(i < rev[i]) std::swap(f[i], f[rev[i]]);
for(reg int p = 2; p <= T_len; p <<= 1){
int half = p >> 1;
int wn = Ksm(3, (mod-1)/p);
if(opt == -1) wn = Ksm(wn, mod-2);
for(reg int i = 0; i < T_len ; i += p){
int buf = 1;
for(reg int k = i; k < i+half; k ++){
int Tmp_1 = 1ll*buf*f[k+half] % mod;
f[k+half] = (f[k] - Tmp_1 + mod) % mod;
f[k] = (f[k] + Tmp_1) % mod;
buf = 1ll*buf*wn % mod;
}
}
}
}