什么是卷积
多项式乘法就是求卷积
1 3 3 4
把这个数组转化成num数组,num[i]表示数字i的有num[i]个。
num = {0 1 0 2 1}
代表0的有0个,1有1个,2有0个,3有2个,4有1个。
使用FFT解决的问题就是num数组和num数组卷积。
num数组和num数组卷积的解决,其实就是从{1 3 3 4}取一个数,从{1 3 3 4}再取一个数,他们的和每个值各有多少个
例如:
{0 1 0 2 1}{0 1 0 2 1} 卷积的结果应该是{0 0 1 0 4 2 4 4 1 }
长度为n的数组和长度为m的数组卷积,结果是长度为n+m-1的数组。
{0 1 0 2 1}{0 1 0 2 1} 卷积的结果应该是{0 0 1 0 4 2 4 4 1 }。
这个结果的意义如下:
从{1 3 3 4}取一个数,从{1 3 3 4}再取一个数
取两个数和为 2 的取法是一种:1+1
和为 4 的取法有四种:1+3, 1+3 ,3+1 ,3+1
和为 5 的取法有两种:1+4 ,4+1;
和为 6的取法有四种:3+3,3+3,3+3,3+3,3+3
和为 7 的取法有四种: 3+4,3+4,4+3,4+3
和为 8 的取法有 一种:4+4
关于多项式
- 次数界:对于多项式A(x),系数为ai,设最高非零系数为ak,任何大于k的整数都是A(x)的次数界
- 系数表达式:
- 系数向量:a=(a0,a1,...,an−1)
- 点值表达方式:{(x0,y0),(x1,y1),...,(xn−1,yn−1)} ,其中xk各不相同,yk=A(xk)
快速傅立叶变换(FFT)
快速计算DFT(离散傅里叶变换)的算法
时间复杂度:O(nlogn)
复数
struct Complex // 复数
{
double r, i;
Complex(double _r = 0, double _i = 0) :r(_r), i(_i) {}
Complex operator +(const Complex &b) {
return Complex(r + b.r, i + b.i);
}
Complex operator -(const Complex &b) {
return Complex(r - b.r, i - b.i);
}
Complex operator *(const Complex &b) {
return Complex(r*b.r - i*b.i, r*b.i + i*b.r);
}
};
FFT模板
递归
Complex* FFT(Complex a[], int n)//n表示向量a的维数 { if(n == 1) return a; Complex wn = Complex(cos(2*PI/n), sin(2*PI/n)); Complex w = Complex(1, 0); Complex* a0 = new Complex[n >> 1]; // new a0[] a1[] size = n/2 Complex* a1 = new Complex[n >> 1]; for(int i = 0; i < n; i++) // a0[] = a[even] a1[] = a[odd] if(i & 1) a1[(i - 1) >> 1] = a[i]; else a0[i >> 1] = a[i]; Complex *y0, *y1; y0 = FFT(a0, n >> 1); // Recursive to solve a0 a1 y1 = FFT(a1, n >> 1); Complex* y = new Complex[n]; for(int k = 0; k < (n >> 1); k++) //sum y0 y1 -> y { y[k] = y0[k] + w*y1[k]; y[k + (n >> 1)] = y0[k] - w*y1[k]; w = w*wn; } delete a0;delete a1;delete y0;delete y1; return y; }
非递归
void change(Complex y[], int n) // 二进制平摊反转置换 O(logn) { int i, j, k; for (i = 1,j = n / 2;i < n - 1;i++) { if (i < j)swap(y[i], y[j]); k = n / 2; while (j >= k) { j -= k; k /= 2; } if (j < k)j += k; } } void fft(Complex y[], int n, int on) //FFT:on=1; IFFT:on=-1 { change(y, n); for (int h = 2;h <= n;h <<= 1) { Complex wn(cos(-on * 2 * PI / h), sin(-on * 2 * PI / h)); for (int j = 0;j < n;j += h) { Complex w(1, 0); for (int k = j;k < j + h / 2;k++) { Complex u = y[k]; Complex t = w*y[k + h / 2]; y[k] = u + t; y[k + h / 2] = u - t; w = w*wn; } } } if (on == -1) for (int i = 0;i < n;i++) y[i].r /= n; }
步骤
- 补0
在两个多项式前面补0,得到两个2n次多项式,设系数向量分别为v1和v2。 - 求值
使用FFT计算f1=DFT(v1)和f2=DFT(v2)。则f1与f2为两个多项式在2n次单位根处的取值(即点值表示)。 - 乘法
把f1与f2每一维对应相乘,得到f,代表对应输入多项式乘积的点值表示。 插值
使用IFFT计算v=IDFT(f),其中v就是乘积的系数向量。