加载中...

FFT

多项式乘法可以用 \(O(n^{2})\) 逐项得到,但不高效。而 FFT 可以 \(O(nlogn)\) 快速计算。

pmMIJ4s.png

  1. 第一步是 系数 \(\rightarrow\) 点值,分别求得 \(A,B\) 两个多项式在 \(n\)\(n\) 次单位根的取值,并将两个多项式在同一个根的取值相乘,作为两个多项式相乘后得到的多项式在 \(n\)\(n\) 次单位根的取值;
  2. 第二步是 点值 \(\rightarrow\) 系数,在已知待求多项式的 \(n\) 个点坐标的情况下,求多项式的 \(n\) 个未知系数,即傅里叶逆变换 (IDFT)。

FFT 的两种实现方式:

  • 递归
  • 迭代倍增

模板

// 适合在没有取模要求,且答案较大的情况使用
#include <complex>
const double PI = acos(-1.0);

// 迭代 FFT,a 为复数向量,inverse 表示是否逆变换
void FFT(vector<complex<double>>& a, bool inverse) {
    int n = a.size();
    // 位逆序置换
    for (int i = 1, j = 0; i < n; i++) {
        int bit = n >> 1;
        for (; j & bit; bit >>= 1) j ^= bit;
        j ^= bit;
        if (i < j) swap(a[i], a[j]);
    }
    // 迭代合并
    for (int len = 2; len <= n; len <<= 1) {
        double ang = 2.0 * PI / len * (inverse ? -1.0 : 1.0);
        complex<double> wlen(cos(ang), sin(ang));
        for (int i = 0; i < n; i += len) {
            complex<double> w(1.0, 0.0);
            for (int j = 0; j < len / 2; j++) {
                complex<double> u = a[i + j];
                complex<double> v = a[i + j + len / 2] * w;
                a[i + j] = u + v;
                a[i + j + len / 2] = u - v;
                w *= wlen;
            }
        }
    }
    if (inverse) {
        for (int i = 0; i < n; i++) a[i] /= n;
    }
}

// 多项式乘法(FFT 版本,返回整数系数,四舍五入)
vector<int> Poly_mul(const vector<int>& a, const vector<int>& b) {
    if (a.empty()) return b;
    if (b.empty()) return a;
    // 小规模暴力乘法(结果不会溢出 int)
    if ((ll)a.size() * b.size() <= 5000) {
        vector<int> c(a.size() + b.size() - 1, 0);
        for (size_t i = 0; i < a.size(); i++) {
            for (size_t j = 0; j < b.size(); j++) {
                c[i + j] += (ll)a[i] * b[j];
            }
        }
        return c;
    }
    int need = (int)a.size() + (int)b.size() - 1;
    int n = 1;
    while (n < need) n <<= 1;
    vector<complex<double>> fa(n, 0.0), fb(n, 0.0);
    for (size_t i = 0; i < a.size(); i++) fa[i] = a[i];
    for (size_t i = 0; i < b.size(); i++) fb[i] = b[i];

    FFT(fa, false);
    FFT(fb, false);
    for (int i = 0; i < n; i++) fa[i] *= fb[i];
    FFT(fa, true);

    vector<int> res(need);
    for (int i = 0; i < need; i++) {
        // 四舍五入,处理可能的负零
        double val = fa[i].real();
        res[i] = (int)llround(val);
    }
    return res;
}

P3803

模板题,\(O(n\log n)\) 求两个 \(n - 1\) 次多项式相乘后,得到的 \(2n-2\) 次多项式的各项系数。

code

P1919

FFT 实现高精度乘法

code

ABC392 G

要找给定数组中满足\(A + C = 2B\)的升序三元组\((A,B,C)\)的数量,可以通过构造多项式的方式巧妙计算:

\(a=[1,2,3,5]\),要计算这个数组中的三元组数量:

转化为计算数组中每个元素作为\(B\)时的数量,再累加到一起。

例如计算\(B=5\)时的三元组数量,即计算\(A+C=10\)\((A,C)\)个数。这相当于计算:

\[num(1)*num(9)+num(2)*num(8)+num(3)*num(7)+num(4)*num(6) \]

其中\(num(i)\)表示数字\(i\)\(a\)中出现个数。

这个操作就是卷积的计算,而计算卷积的高效算法就是FFT。对于本题,可以构造这样的多项式:

\[(x^{1} + x^{2} + x^{3} + x^{5}) \]

数组中每一项对应指数,而数组中出现了的数系数置为1,没出现的数系数置为0。计算这个多项式的平方,即:

\[(x^{1} + x^{2} + x^{3} + x^{5})(x^{1} + x^{2} + x^{3} + x^{5}) \]

上面多项式的展开结果中的每一项\(b_{i}x^{i}\)即表示\(2B=i\)\((A,C)\)个数是\(b_{i}\)。具体见代码(用python中的numpy库实现了FFT)

code

posted @ 2025-02-09 09:49  小橘奏  阅读(113)  评论(0)    收藏  举报