离散傅里叶变换是一种快速进行多项式乘法的方法,朴素的多项式乘法时间复杂度一般为,通过离散傅里叶变换,时间复杂度可以优化到

多项式的表示方法

系数表示法

我一直以来学习的多项式表示方法都是系数表示法,简单来说就是 其中阶多项式

其中阶多项式

那么对于这两个多项式的乘积,就是
高中知识,用中每一项乘中每一项,然后合并同类项,显然复杂度是

点值表示法

众所周知个点可以确定一个阶的多项式,所以一个阶多项式可以表示成

那么相乘就可以写成

只需要的复杂度就可以完成

系数表示法转点值表示法

朴素的方法就是一个个算,每算一次就要的复杂度,算次,需要的复杂度,得不偿失
而离散傅里叶变换就是一种能够在的时间复杂度内将系数表示法转换为点值表示法的算法
考虑我们想要的其实就是能够快速得算出每一个点值的大小,如果就可以方便得算出答案,但是显然实数范围内只有这两个特殊得数,对于阶数足够高的题,起不了多大的作用,所以我们就去复数范围内找答案

单位复根

在负数里,一个负数可以表示为复平面上的一个向量,两个复数相乘可以表示为向量的模相乘幅角相加
也就是说,如果有一个复数所表示的向量恰好落在以(0,0)为圆心的单位圆上,幅角为,那么就是了。
同时也是,以此类推,的整数次幂的次幂
其中就被称为单位复根
图片说明
如图,红色的向量就是的单位复根,绿色的是的单位复根,蓝色的是
单位复根可以表示为一个复数同时根据欧拉公式,又

单位复根的性质

对于任意的正整数和整数

快速傅里叶变换

考虑一个项的多项式
按下标的奇偶划分
对奇数项提取公因子

整理一下


带入
可以发现非常相似,仅仅只是系数的不同,所以可以递归得分治来解决这个问题

我们假设表示的值
在我们求时,先递归的求解的值,则
同时根据

所以我们只需要遍历一遍就可以得到所有的答案了

递归版离散傅里叶变换模板

void DFT(Comp f[], int n, int rev)//f[i]表示x=(w_n)^i的值
{
  if (n == 1) return;
  for (int i = 0; i < n; ++i) tmp[i] = f[i];
  for (int i = 0; i < n; ++i) {  //偶数放左边,奇数放右边
    if (i & 1)
      f[n / 2 + i / 2] = tmp[i];
    else
      f[i / 2] = tmp[i];
  }
  Comp *g = f, *h = f + n / 2;
  DFT(g, n / 2, rev);
  DFT(h, n / 2, rev);  //递归DFT
  Comp cur(1, 0), wn(cos(2 * M_PI / n), sin(2 * M_PI * rev / n));
  for (int k = 0; k < n / 2; ++k) {
    tmp[k] = g[k] + cur * h[k];
    tmp[k + n / 2] = g[k] - cur * h[k];
    cur = cur * wn;//只需要每次都乘w_n就可以得到每一个单位复根
  }
  for (int i = 0; i < n; ++i) f[i] = tmp[i];
}

蝴蝶变换

列表发现,序列中每一项在递归终点的位置为它下标二进制表示反转之后的值
例如在的离散傅里叶变换中,的二进制表示是,在递归终点时,它的位置为,即
如下图所示
图片说明
我们可以动态规划来求解每个数转换后下标的位置

dp实现

为原下标为的系数最终的位置
显然
对于任意的,他的二进制表示为

的二进制表示为

翻转后,也就是的二进制表示为

的二进制表示为

可以发现

for (int i = 0; i < len; ++i)
{
    pos[i] = (pos[i >> 1] >> 1) | ((i & 1) << (l - 1));
}

于是fft的代码变为

void change(comp y[],int len)
{
    for (int i = 0; i < len; ++i)
    {
        if(i<pos[i])
            swap(y[i], y[pos[i]]);
    }
}

void DFT(comp y[],int len,int rev)
{
    change(y,len);
    for(int h = 2; h <= len; h <<= 1)
    {
        comp wn(cos(2 * PI / h), sin(rev * 2 * PI / h));
        for (int j = 0; j < len; j += h)
        {
            comp w(1,0);
            for (int k = j; k < j + h / 2; k++)
            {
                comp u = y[k];
                comp t = w*y[k+h/2];
                y[k] = u+t;
                y[k+h/2] = u-t;
                w = w * wn;
            }
        }
    }
    if (rev == -1)
        for(int i = 0;i < len;i++)
            y[i].r /= len;
}