TLEer's Blog

FFT目害学笔记

TLEer's Blog

前置知识

  • 点值表示法
    • 可以用 n+1n+1 个点来表示一个 nn 次的多项式
    • 两个多项式的点值表示相乘就可以得出第三个多项式的点值表示(但是只有 n+1n+1 个点,而不是 2n+12*n+1 个,但是多找几个点不就行了)
      有一个问题:你还得把第三个多项式的点值表示用插值转换成系数表示
TLEer's Blog

前置知识

  • 关于单位根
    • 单位根是复数,满足相乘时模长相乘,辐角相加
    • 值为1的单位根是第0个
    • 如果 nn 是偶数 ωn(k+n/2)=ωnk\omega_n^{(k+n/2)} = \omega_n^k
TLEer's Blog

复数板子(STL也有,别用)

struct Complex {
  explicit Complex(double xx = 0, double yy = 0) { x = xx, y = yy; }
  double x, y;
  Complex operator+(Complex const &b) const { return Complex(x + b.x, y + b.y); }
  Complex operator-(Complex const &b) const { return Complex(x - b.x, y - b.y); }
  Complex operator*(Complex const &b) const { return Complex(x * b.x - y * b.y, x * b.y + y * b.x); }
  Complex operator/(Complex const &b) const {
    double t = b.x * b.x + b.y * b.y;
    return Complex((x * b.x + y * b.y) / t,
                         (y * b.x - x * b.y) / t);
  }
} a, b;

预处理单位根

Complex dlt(cos(2 * Pi / n), sin(2 * Pi / n)), buf(1, 0);
for (int i = 0; i < n; i++) {
  w[i] = buf;
  buf = buf * dlt;
}

Pi = acos(-1);

TLEer's Blog

把单位根扔进多项式里

F(x)=F[0]+F[1]x+F[2]x2+F[3]x3++F[n2]xn/21F(x) = F[0] + F[1]x + F[2]x^2 + F[3]x^3 + \cdots + F[n-2]x^{n/2-1}
保证 nn22 的整幂,不会出现两边不均匀的情况(上面是有 nn 项即 n1n-1 次的多项式)
FL(x)=F[0]+F[2]x+F[4]x2++F[n2]xn/21FL(x) = F[0] + F[2]x + F[4]x^2 + \cdots + F[n-2]x^{n/2-1}

FR(x)=F[1]+F[3]x+F[5]x2++F[n1]xn/21FR(x) = F[1] + F[3]x + F[5]x^2 + \cdots + F[n-1]x^{n/2-1}

  • k<n/2k < n/2,将 ωnk\omega_n^k 代入上述式子(上半圆)
    F(ωnk)=FL((ωnk)2)+ωnkFR((ωnk)2)F(\omega_n^k) = FL((\omega_n^k)^2) + \omega_n^kFR((\omega_n^k)^2)

    F(ωnk)=FL(ωn2k)+ωnkFR(ωn2k)F(\omega_n^k) = FL(\omega_n^{2k}) + \omega_n^kFR(\omega_n^{2k})

    F(ωnk)=FL(ωn/2k)+ωnkFR(ωn/2k)F(\omega_n^k) = FL(\omega_{n/2}^{k}) + \omega_n^kFR(\omega_{n/2}^{k})

TLEer's Blog
  • k<n/2k < n/2,将 ωnk+n/2\omega_n^{k+n/2} 代入上述式子(下半圆)

    F(ωnk+n/2)=FL((ωnk+n/2)2)+ωnk+n/2FR((ωnk+n/2)2)F(\omega_n^{k+n/2}) = FL((\omega_n^{k+n/2})^2) + \omega_n^{k+n/2}FR((\omega_n^{k+n/2})^2)

    F(ωnk+n/2)=FL(ωn2k+n)+ωnkFR(ωn2k+n)F(\omega_n^{k+n/2}) = FL(\omega_n^{2k+n}) + \omega_n^kFR(\omega_n^{2k+n})

    F(ωnk+n/2)=FL(ωn2k)+ωnkFR(ωn2k)F(\omega_n^{k+n/2}) = FL(\omega_n^{2k}) + \omega_n^kFR(\omega_n^{2k})

    F(ωnk+n/2)=FL(ωn/2k)ωnkFR(ωn/2k)F(\omega_n^{k+n/2}) = FL(\omega_{n/2}^{k}) - \omega_n^kFR(\omega_{n/2}^{k})

这样在已知 FLFLFRFRωn/20,ωn/21,,ωn/2n/21\omega_{n/2}^{0},\omega_{n/2}^{1},\cdots,\omega_{n/2}^{n/2-1} 的值时就可以得出 F(x)F(x)ωn/20,ωn/21,,ωn/2n/21\omega_{n/2}^{0},\omega_{n/2}^{1},\cdots,\omega_{n/2}^{n/2-1}ωn/2n/2,ωn/2n/2+1,,ωn/2n1\omega_{n/2}^{n/2},\omega_{n/2}^{n/2+1},\cdots,\omega_{n/2}^{n-1} 处的点值表示了

TLEer's Blog

然而

...
你并不知道这些值

TLEer's Blog

又因为这个 FLFLFRFR 也可以以同样的方式划分,所以就可以用分治搞定它

TLEer's Blog

把系数表达转换为点值表达”的算法叫做DFT

#define Maxn 1350000
using namespace std;
const double Pi = acos(-1);
int n, m;
Complex f[Maxn << 1], sav[Maxn << 1];
void Dft(Complex *f, int len) {
  if (len == 1)return;//边界
  //指针的使用比较巧妙
  Complex *fl = f, *fr = f + len / 2;
  for (int k = 0; k < len; k++)sav[k] = f[k];
  for (int k = 0; k < len / 2; k++)//分奇偶打乱
  {
    fl[k] = sav[k << 1];
    fr[k] = sav[k << 1 | 1];
  }
  Dft(fl, len / 2);
  Dft(fr, len / 2);//处理子问题
  //由于每次使用的单位根次数不同(len次单位根),所以要重新求。
  Complex dlt(cos(2 * Pi / len), sin(2 * Pi / len)), buf(1, 0);
  for (int k = 0; k < len / 2; k++) {
    //这里buf = (len次单位根的第k个)
    sav[k] = fl[k] + buf * fr[k];//(1)
    sav[k + len / 2] = fl[k] - buf * fr[k];//(2)
    //这两条语句具体见Tips的式子
    buf = buf * dlt;//得到下一个单位根。
  }
  for (int k = 0; k < len; k++)f[k] = sav[k];
}
int main() {
  scanf("%d", &n);
  for (int i = 0; i < n; i++)scanf("%lf", &f[i].x);
  //一开始都是实数,虚部为0
  for (m = 1; m < n; m <<= 1);
  //把长度补到2的幂,不必担心高次项的系数,因为默认为0
  Dft(f, m);
  for (int i = 0; i < m; ++i)
    printf("(%.4f,%.4f)\n", f[i].x, f[i].y);
  return 0;
}
TLEer's Blog

那咋把点值表示转回系数呢?

用InverseDFT

TLEer's Blog

IDFT

先说结论 : 把DFT中的 ωn1\omega^1_n 换成 ωn1\omega^{-1}_n ,做完之后除以 nn 即可。其中, ωn1\omega^{-1}_nωn1\omega^{1}_n 的共轭

证明:不会

TLEer's Blog

code:

void IDft(Complex *f, int len) {
  if (len == 1)return;
  Complex *fl = f, *fr = f + len / 2;
  for (int k = 0; k < len; k++)sav[k] = f[k];
  for (int k = 0; k < len / 2; k++) {
    fl[k] = sav[k << 1];
    fr[k] = sav[k << 1 | 1];
  }
  IDft(fl, len / 2);
  IDft(fr, len / 2);
  Complex dlt(cos(2 * Pi / len), -sin(2 * Pi / len)), buf(1, 0);
  for (int k = 0; k < len / 2; k++) {
    sav[k] = fl[k] + buf * fr[k];//(1)
    sav[k + len / 2] = fl[k] - buf * fr[k];//(2)
    buf = buf * dlt;
  }
  for (int k = 0; k < len; k++)f[k] = sav[k];
}
TLEer's Blog

还有优化……

但是裸的递归版能A掉板子就离谱
记录

TLEer's Blog

Reference:

Command-block's Blog