形如 a+bi ,其中 a∈R , b∈R , i2=−1 的数称为复数,记作 z=a+bi∈C 。
可以证明,在复数域中,下式依然成立:
令 z=ix ,立即得到:
对任意复数 z=a+bi≠0 ,必定存在唯一的 0≤θ<2π 和唯一的 r>0 满足:
其中 |z|=r=a2+b2 称作复数 z 的模, θ 称作复数 z 的辐角主值。
把复数 z=a+bi 看作平面直角坐标系中的点 (a,b) , r , θ 便有了明确的几何意义。此平面叫做复平面。
注意到
所以在复平面中复数加减按平行四边形法则,复数乘除为模乘除、辐角加减。
考虑方程
有 n 个两两不同的解分别为
给出 n − 1 次多项式有多种方法,最常见的是给出每一项前面的系数,即:
还有其他的方法,即给出 n 个两两不同的位置处 f(x) 的函数值。
显然如果要做乘法,第一个的复杂度需要 O(n2) ,而第二个的复杂度只需 O(n) 。
快速傅立叶变换是用 O(nlogn) 的时间把前者转换到后者、把后者转化到前者,这样我们就可以 O(nlogn) 完成一次前者的乘法了。
我们首先要把 n 上调至 2 的幂,后面本来没有的系数都设置为 0 就好了。
然后我们取“ n 个两两不同的位置”为 n 个 n 次单位根,记录 m=n2 ,则
分别拆出奇数项和偶数项,直接递归即可。时间复杂度 O(nlogn) 。
虽然复杂度没啥问题,但是递归版的快速傅立叶变换常数还是非常大的,所以可能需要优化常数。
考虑分治的过程,不停的拆分奇数项和偶数项,所以我们直接把标号全部倒置,从低位开始不停的合并 0/1 就好了,非递归版的常数还是可以接受的。
#include
#include
#include
#include
#include
using namespace std;
const double pi = acos(-1);
//定义复数类型
struct Com{
double x, y;
Com(double X = 0, double Y = 0){x = X, y = Y;}
Com operator + (const Com &t) const{return Com(x+t.x, y+t.y);}
Com operator - (const Com &t) const{return Com(x-t.x, y-t.y);}
Com operator * (const Com &t) const{return Com(x*t.x - y*t.y, y*t.x + x*t.y);}
};
//A一开始是系数数组,在调用完函数后,会变成记录答案的数组,答案记录在实部
void fft(Com *A, int n, int f){
//仅有一个数的时候直接返回,这时的系数恰好是答案
if(n == 1) return;
int m = n/2;
Com C1[m], C2[m];
//分奇偶划分,递归
for(int i = 0; i < m; i ++) C1[i] = A[i*2], C2[i] = A[i*2+1];
fft(C1, m, f), fft(C2, m, f);
//k等与0的时候的单位元,k等于1的时候的数值
Com t1(1, 0), t2(cos(pi/m), f*sin(pi/m));
//当i大于m时,$e^{i\pi}=-1$,所以后面的项顺便算出来
for(int i = 0; i < m; i ++){
A[i] = C1[i] + t1 * C2[i];
A[i+m] = C1[i] - t1 * C2[i];
//让k增大
t1 = t1 * t2;
}
}
Com a[300010], b[300010];
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);
//为了容纳答案的长度
m += n;
//最多有n+m+1项,"n <= m" '=' 不能省略
for(n = 1; n <= m; n <<= 1);
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);
//有浮点误差,+0.5消除
for(int i = 0; i <= m; i ++) printf("%d ", int((a[i].x/n+0.5)));
return 0;
}
//与上边一样的就不打了
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 300010;
const double pi = acos(-1);
int Bit, rx[maxn];
struct Com{
double x, y;
Com(double X = 0, double Y = 0){x = X, y = Y;}
Com(const Com &t){x = t.x, y = t.y;}
Com operator + (const Com &t) const{return Com(x+t.x, y+t.y);}
Com operator - (const Com &t) const{return Com(x-t.x, y-t.y);}
Com operator * (const Com &t) const{return Com(x*t.x - y*t.y, y*t.x + x*t.y);}
};
void fft(Com *A, int n, int f){
//预处理二进制翻转之后的值是多少,只会执行一次
//原理:100111 进行反转,我们得到 10011 的翻转 110010,右移一位,空出最高位,把之前的那个补上即可
if(rx[n-1] != n-1) for(int i = 0; i < n; i ++) rx[i] = (rx[i>>1]>>1) | ((i&1) << (Bit-1));
//进行位数交换,只在有特定条件的时候进行
for(int i = 0; i < n; i ++) if(i < rx[i]) swap(A[i], A[rx[i]]);
//长度 -> 开头 -> 每一项
for(int i = 1; i < n; i <<= 1){
const Com Base(cos(pi/i), f*sin(pi/i));
for(int j = 0; j < n; j += i << 1){
Com mi(1, 0);
for(int k = 0; k < i; k ++, mi = mi * Base){
Com x = A[j+k], y = A[i+j+k] * mi;
//这一句等价于:当i大于m时,$e^{i\pi}=-1$,所以后面的项顺便算出来
A[j+k] = x+y, A[i+j+k] = x-y;
}
}
}
}
Com a[maxn], b[maxn];
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);
m += n;
for(n = 1; n <= m; n <<= 1, Bit ++);
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].x/n+0.5)));
puts("");
return 0;
}