使用FFT(快速傅里叶变换)实现高精度大整数乘法

一、题目

众所周知 ,快速傅里叶变换(Fast Fourier Transform, FFT)能够将计算DFT(离散傅里叶变换)的复杂度从只用DFT定义计算需要的 O ( n 2 ) O(n^2) O(n2),降低到 O ( n log ⁡ n ) O(n\log n) O(nlogn) ,其中 n 为数据大小,因此,FFT常被用于加速高精度乘法中。那么问题来了,给定两个非负整数 A 和 B ,请输出它们的乘积 A ∗ B A * B AB


二、FFT

简单介绍

FFT是DFT的快速算法,它能够将多项式乘法转变成点值乘法,时间复杂度也从 O ( n 2 ) O(n^2) O(n2)降低到了 O ( n log ⁡ n ) O(n\log n) O(nlogn)

如果将x=10代入多项式中,那么多项式的系数就是对应的数位,所以完全可以用FFT来进行大整数乘法的运算。


如何转换为点值乘法

假设我们现在有两个多项式,它们分别为

f ( x ) = 1 + 2 x + 3 x 2 g ( x ) = x + 2 x 2 \begin{aligned} &f(x) = 1+2x + 3x^2\\&g(x)=x + 2x^2\end{aligned} f(x)=1+2x+3x2g(x)=x+2x2

它们的乘积 h ( x ) = f ( x ) g ( x ) = a 0 + a 1 x + a 2 x 2 + a 3 x 3 + a 4 x 4 h(x)=f(x)g(x)=a_0+a_1x+a_2x^2+a_3x^3+a_4x^4 h(x)=f(x)g(x)=a0+a1x+a2x2+a3x3+a4x4

如果取 x = [ 1 , 2 , 3 ] x=[1,2,3] x=[1,2,3]

x123
h(x)18272714

[ a 0 a 1 a 2 a 3 a 4 ] ∗ [ 1 1 1 x 0 x 1 x 2 x 0 2 x 1 2 x 2 2 x 0 3 x 1 3 x 2 3 x 0 4 x 1 4 x 2 4 ] = [ h ( x 0 ) h ( x 1 ) h ( x 2 ) ] \begin{bmatrix}a_0&a_1&a_2&a_3&a_4\end{bmatrix}* \begin{bmatrix}1&1&1\\x_0&x_1&x_2\\x_0^2&x_1^2&x_2^2\\x_0^3&x_1^3&x_2^3\\x_0^4&x_1^4&x_2^4\end{bmatrix} = \begin{bmatrix}h(x_0)&h(x_1)&h(x_2)\end{bmatrix} [a0a1a2a3a4] 1x0x02x03x041x1x12x13x141x2x22x23x24 =[h(x0)h(x1)h(x2)]

据此可以求出 a 0 , a 1 , a 2 , a 3 , a 4 a_0,a_1,a_2,a_3,a_4 a0,a1,a2,a3,a4

如果我们使用代码实现以上逻辑的话,时间复杂度为 O ( n 2 ) O(n^2) O(n2) ,这并没有产生优化。

为什么呢?

这是因为我们的取值是随机的,那么就猜想如果对 x 0 , x 1 . . . x n x_0,x_1...x_n x0,x1...xn的取值符合某种规律,运用这种规律的性质是否可以产生优化。

答案是肯定的,这就需要用到复数的知识了:

在这里插入图片描述

  • 在复平面上,我们作出一个单位圆,易知 x 8 = 1 x^8=1 x8=1的解为A、B…H这八个点,不妨把A点记为 ω 8 0 \omega_8^0 ω80,将 ω 8 0 \omega_8^0 ω80不断旋转45°后,我们可以依次得到 ω 8 1 , ω 8 2 . . . ω 8 7 \omega_8^1,\omega_8^2...\omega_8^7 ω81,ω82...ω87,这些点对应着 x 8 = 1 x^8=1 x8=1的解,所以 ω 8 8 = ω 8 0 = 1 \omega_8^8=\omega_8^0=1 ω88=ω80=1,其中, ω 8 \omega_8 ω8叫做旋转因子

  • 而如果我们把 x 8 = 1 x^8=1 x8=1变为 x 4 = 1 x^4=1 x4=1 ω 8 \omega_8 ω8变为 ω 4 \omega_4 ω4,那么可以得到 ω 4 1 = ω 8 2 , ω 4 0 = − ω 8 4 \omega_4^1=\omega_8^2,\omega_4^0=-\omega_8^4 ω41=ω82,ω40=ω84

在知道了这些以后,我们就可以把 x ∈ { ω 8 0 , ω 8 1 . . . ω 8 7 } x\in \{\omega_8^0,\omega_8^1...\omega_8^7\} x{ω80,ω81...ω87}代入到 f ( x ) = a 0 + a 1 x + a 2 x 2 + a 3 x 3 + a 4 x 4 + a 5 x 5 + a 6 x 6 + a 7 x 7 f(x)=a_0+a_1x+a_2x^2+a_3x^3+a_4x^4+a_5x^5+a_6x^6+a_7x^7 f(x)=a0+a1x+a2x2+a3x3+a4x4+a5x5+a6x6+a7x7

f ( x ) = a 0 + a 1 x + a 2 x 2 + a 3 x 3 + a 4 x 4 + a 5 x 5 + a 6 x 6 + a 7 x 7 x ∈ { ω 8 0 , ω 8 1 . . . ω 8 7 } f ( x ) = a 0 x 0 + a 2 x 2 + a 4 x 4 + a 6 x 6 + x ( a 1 x 0 + a 3 x 2 + a 5 x 4 + a 7 x 6 ) 设 p 1 ( x ) = a 0 x 0 + a 2 x 1 + a 4 x 2 + a 6 x 3 ,   p 2 ( x ) = a 1 x 0 + a 3 x 1 + a 5 x 2 + a 7 x 3 那么 f ( x ) = p 1 ( x 2 ) + x p 2 ( x 2 ) , f ( − x ) = p 1 ( x 2 ) − x p 2 ( x 2 ) 这就将 1 个 8 项多项式的计算转换成了 2 个 4 项多项式的子问题计算, 这显然是分治的思想 之后再根据 ω 4 1 = ω 8 2 , ω 4 0 = − ω 8 4 , 就将子问题解决了 于是,我们就得到了 f ( ω 8 0 ) = p 1 ( w 4 0 ) + ω 4 0 p 2 ( ω 4 0 ) f ( ω 8 1 ) = p 1 ( w 4 1 ) + ω 4 1 p 2 ( ω 4 1 ) f ( ω 8 2 ) = p 1 ( w 4 2 ) + ω 4 2 p 2 ( ω 4 2 ) f ( ω 8 3 ) = p 1 ( w 4 3 ) + ω 4 3 p 2 ( ω 4 3 ) f ( ω 8 4 ) = f ( ω 8 4 ∗ ω 8 0 ) = f ( − ω 4 0 ) = p 1 ( ω 4 0 ) − ω 4 0 p 2 ( ω 4 0 ) f ( ω 8 5 ) = p 1 ( w 4 1 ) − ω 4 1 p 2 ( ω 4 1 ) f ( ω 8 6 ) = p 1 ( w 4 2 ) − ω 4 2 p 2 ( ω 4 2 ) f ( ω 8 7 ) = p 1 ( w 4 3 ) − ω 4 3 p 2 ( ω 4 3 ) \begin{aligned} &f(x)=a_0+a_1x+a_2x^2+a_3x^3+a_4x^4+a_5x^5+a_6x^6+a_7x^7 \\ &x\in \{\omega_8^0,\omega_8^1...\omega_8^7\} \\ &f(x)=a_0x^0+a_2x^2+a_4x^4+a_6x^6+x(a_1x^0+a_3x^2+a_5x^4+a_7x^6) \\ &设p_1(x)=a_0x^0+a_2x^1+a_4x^2+a_6x^3,\ p_2(x)=a_1x^0+a_3x^1+a_5x^2+a_7x^3 \\ &那么f(x)=p_1(x^2)+xp_2(x^2),f(-x)=p_1(x^2)-xp_2(x^2) \\ &这就将1个8项多项式的计算转换成了2个4项多项式的子问题计算, \\ &这显然是分治的思想 \\ &之后再根据\omega_4^1=\omega_8^2,\omega_4^0=-\omega_8^4,就将子问题解决了 \\ &于是,我们就得到了 \\ &f(\omega_8^0)=p_1(w_4^0)+\omega_4^0p_2(\omega_4^0) \\ &f(\omega_8^1)=p_1(w_4^1)+\omega_4^1p_2(\omega_4^1) \\ &f(\omega_8^2)=p_1(w_4^2)+\omega_4^2p_2(\omega_4^2) \\ &f(\omega_8^3)=p_1(w_4^3)+\omega_4^3p_2(\omega_4^3) \\ &f(\omega_8^4)=f(\omega_8^4*\omega_8^0)=f(-\omega_4^0)=p_1(\omega_4^0)-\omega_4^0p_2(\omega_4^0) \\ &f(\omega_8^5)=p_1(w_4^1)-\omega_4^1p_2(\omega_4^1) \\ &f(\omega_8^6)=p_1(w_4^2)-\omega_4^2p_2(\omega_4^2) \\ &f(\omega_8^7)=p_1(w_4^3)-\omega_4^3p_2(\omega_4^3) \end{aligned} f(x)=a0+a1x+a2x2+a3x3+a4x4+a5x5+a6x6+a7x7x{ω80,ω81...ω87}f(x)=a0x0+a2x2+a4x4+a6x6+x(a1x0+a3x2+a5x4+a7x6)p1(x)=a0x0+a2x1+a4x2+a6x3, p2(x)=a1x0+a3x1+a5x2+a7x3那么f(x)=p1(x2)+xp2(x2),f(x)=p1(x2)xp2(x2)这就将18项多项式的计算转换成了24项多项式的子问题计算,这显然是分治的思想之后再根据ω41=ω82,ω40=ω84,就将子问题解决了于是,我们就得到了f(ω80)=p1(w40)+ω40p2(ω40)f(ω81)=p1(w41)+ω41p2(ω41)f(ω82)=p1(w42)+ω42p2(ω42)f(ω83)=p1(w43)+ω43p2(ω43)f(ω84)=f(ω84ω80)=f(ω40)=p1(ω40)ω40p2(ω40)f(ω85)=p1(w41)ω41p2(ω41)f(ω86)=p1(w42)ω42p2(ω42)f(ω87)=p1(w43)ω43p2(ω43)

对f(x)进行的这种变换就叫做FFT(快速傅里叶变换)

补充

FFT的计算必须保证多项式的项数是2的整数次幂,如果不足的话,那就在后面补0(令系数为0)


三、IFFT

至此,我们已经解决了把多项式乘法转换成点值乘法的问题,即 A ∗ Ω ⇒ F F T R A*\Omega\stackrel{FFT}{\Rightarrow} R AΩFFTR

A = [ a 0 a 1 a 2 a 3 a 4 a 5 a 6 a 7 ] ,   Ω = [ ω 0 ∗ 0 ω 1 ∗ 0 ω 2 ∗ 0 ω 3 ∗ 0 ω 4 ∗ 0 ω 5 ∗ 0 ω 6 ∗ 0 ω 7 ∗ 0 ω 0 ∗ 1 ω 1 ∗ 1 ω 2 ∗ 1 ω 3 ∗ 1 ω 4 ∗ 1 ω 5 ∗ 1 ω 6 ∗ 1 ω 7 ∗ 1 ω 0 ∗ 2 ω 1 ∗ 2 ω 2 ∗ 2 ω 3 ∗ 2 ω 4 ∗ 2 ω 5 ∗ 2 ω 6 ∗ 2 ω 7 ∗ 2 ω 0 ∗ 3 ω 1 ∗ 3 ω 2 ∗ 3 ω 3 ∗ 3 ω 4 ∗ 3 ω 5 ∗ 3 ω 6 ∗ 3 ω 7 ∗ 3 ω 0 ∗ 4 ω 1 ∗ 4 ω 2 ∗ 4 ω 3 ∗ 4 ω 4 ∗ 4 ω 5 ∗ 4 ω 6 ∗ 4 ω 7 ∗ 4 ω 0 ∗ 5 ω 1 ∗ 5 ω 2 ∗ 5 ω 3 ∗ 5 ω 4 ∗ 5 ω 5 ∗ 5 ω 6 ∗ 5 ω 7 ∗ 5 ω 0 ∗ 6 ω 1 ∗ 6 ω 2 ∗ 6 ω 3 ∗ 6 ω 4 ∗ 6 ω 5 ∗ 6 ω 6 ∗ 6 ω 7 ∗ 6 ω 0 ∗ 7 ω 1 ∗ 7 ω 2 ∗ 7 ω 3 ∗ 7 ω 4 ∗ 7 ω 5 ∗ 7 ω 6 ∗ 7 ω 7 ∗ 7 ] A=\begin{bmatrix}a_0&a_1&a_2&a_3&a_4&a_5&a_6&a_7\end{bmatrix},\ \Omega=\begin{bmatrix}\omega^{0*0}&\omega^{1*0}&\omega^{2*0}&\omega^{3*0}&\omega^{4*0}&\omega^{5*0}&\omega^{6*0}&\omega^{7*0}\\\omega^{0*1}&\omega^{1*1}&\omega^{2*1}&\omega^{3*1}&\omega^{4*1}&\omega^{5*1}&\omega^{6*1}&\omega^{7*1}\\\omega^{0*2}&\omega^{1*2}&\omega^{2*2}&\omega^{3*2}&\omega^{4*2}&\omega^{5*2}&\omega^{6*2}&\omega^{7*2}\\\omega^{0*3}&\omega^{1*3}&\omega^{2*3}&\omega^{3*3}&\omega^{4*3}&\omega^{5*3}&\omega^{6*3}&\omega^{7*3}\\\omega^{0*4}&\omega^{1*4}&\omega^{2*4}&\omega^{3*4}&\omega^{4*4}&\omega^{5*4}&\omega^{6*4}&\omega^{7*4}\\\omega^{0*5}&\omega^{1*5}&\omega^{2*5}&\omega^{3*5}&\omega^{4*5}&\omega^{5*5}&\omega^{6*5}&\omega^{7*5}\\\omega^{0*6}&\omega^{1*6}&\omega^{2*6}&\omega^{3*6}&\omega^{4*6}&\omega^{5*6}&\omega^{6*6}&\omega^{7*6}\\\omega^{0*7}&\omega^{1*7}&\omega^{2*7}&\omega^{3*7}&\omega^{4*7}&\omega^{5*7}&\omega^{6*7}&\omega^{7*7}\end{bmatrix} A=[a0a1a2a3a4a5a6a7], Ω= ω00ω01ω02ω03ω04ω05ω06ω07ω10ω11ω12ω13ω14ω15ω16ω17ω20ω21ω22ω23ω24ω25ω26ω27ω30ω31ω32ω33ω34ω35ω36ω37ω40ω41ω42ω43ω44ω45ω46ω47ω50ω51ω52ω53ω54ω55ω56ω57ω60ω61ω62ω63ω64ω65ω66ω67ω70ω71ω72ω73ω74ω75ω76ω77

现在,我们要做的就是根据 R R R求出 A A A,这一步叫做IFFT(逆快速傅里叶变换)

R ∗ Ω − 1 ⇒ I F F T A R*\Omega^{-1}\stackrel{IFFT}{\Rightarrow}A RΩ1IFFTA

Ω \Omega Ω​矩阵本质上是个范德蒙德矩阵,我们可以根据性质得到其逆矩阵为

Ω − 1 = 1 8 [ τ 0 ∗ 0 τ 1 ∗ 0 τ 2 ∗ 0 τ 3 ∗ 0 τ 4 ∗ 0 τ 5 ∗ 0 τ 6 ∗ 0 τ 7 ∗ 0 τ 0 ∗ 1 τ 1 ∗ 1 τ 2 ∗ 1 τ 3 ∗ 1 τ 4 ∗ 1 τ 5 ∗ 1 τ 6 ∗ 1 τ 7 ∗ 1 τ 0 ∗ 2 τ 1 ∗ 2 τ 2 ∗ 2 τ 3 ∗ 2 τ 4 ∗ 2 τ 5 ∗ 2 τ 6 ∗ 2 τ 7 ∗ 2 τ 0 ∗ 3 τ 1 ∗ 3 τ 2 ∗ 3 τ 3 ∗ 3 τ 4 ∗ 3 τ 5 ∗ 3 τ 6 ∗ 3 τ 7 ∗ 3 τ 0 ∗ 4 τ 1 ∗ 4 τ 2 ∗ 4 τ 3 ∗ 4 τ 4 ∗ 4 τ 5 ∗ 4 τ 6 ∗ 4 τ 7 ∗ 4 τ 0 ∗ 5 τ 1 ∗ 5 τ 2 ∗ 5 τ 3 ∗ 5 τ 4 ∗ 5 τ 5 ∗ 5 τ 6 ∗ 5 τ 7 ∗ 5 τ 0 ∗ 6 τ 1 ∗ 6 τ 2 ∗ 6 τ 3 ∗ 6 τ 4 ∗ 6 τ 5 ∗ 6 τ 6 ∗ 6 τ 7 ∗ 6 τ 0 ∗ 7 τ 1 ∗ 7 τ 2 ∗ 7 τ 3 ∗ 7 τ 4 ∗ 7 τ 5 ∗ 7 τ 6 ∗ 7 τ 7 ∗ 7 ] , τ = ω − 1 \Omega^{-1}=\frac18\begin{bmatrix}\tau^{0*0}&\tau^{1*0}&\tau^{2*0}&\tau^{3*0}&\tau^{4*0}&\tau^{5*0}&\tau^{6*0}&\tau^{7*0}\\\tau^{0*1}&\tau^{1*1}&\tau^{2*1}&\tau^{3*1}&\tau^{4*1}&\tau^{5*1}&\tau^{6*1}&\tau^{7*1}\\\tau^{0*2}&\tau^{1*2}&\tau^{2*2}&\tau^{3*2}&\tau^{4*2}&\tau^{5*2}&\tau^{6*2}&\tau^{7*2}\\\tau^{0*3}&\tau^{1*3}&\tau^{2*3}&\tau^{3*3}&\tau^{4*3}&\tau^{5*3}&\tau^{6*3}&\tau^{7*3}\\\tau^{0*4}&\tau^{1*4}&\tau^{2*4}&\tau^{3*4}&\tau^{4*4}&\tau^{5*4}&\tau^{6*4}&\tau^{7*4}\\\tau^{0*5}&\tau^{1*5}&\tau^{2*5}&\tau^{3*5}&\tau^{4*5}&\tau^{5*5}&\tau^{6*5}&\tau^{7*5}\\\tau^{0*6}&\tau^{1*6}&\tau^{2*6}&\tau^{3*6}&\tau^{4*6}&\tau^{5*6}&\tau^{6*6}&\tau^{7*6}\\\tau^{0*7}&\tau^{1*7}&\tau^{2*7}&\tau^{3*7}&\tau^{4*7}&\tau^{5*7}&\tau^{6*7}&\tau^{7*7}\end{bmatrix},\tau=\omega^{-1} Ω1=81 τ00τ01τ02τ03τ04τ05τ06τ07τ10τ11τ12τ13τ14τ15τ16τ17τ20τ21τ22τ23τ24τ25τ26τ27τ30τ31τ32τ33τ34τ35τ36τ37τ40τ41τ42τ43τ44τ45τ46τ47τ50τ51τ52τ53τ54τ55τ56τ57τ60τ61τ62τ63τ64τ65τ66τ67τ70τ71τ72τ73τ74τ75τ76τ77 ,τ=ω1

这样一来,求IFFT与求FFT没有什么区别了,因此求FFT的函数也可用于求IFFT,我们只需要引入一个参数inverse,若其为true,则是求IFFT,将 ω \omega ω变为 τ \tau τ,若为false,就是求FFT



四、代码

根据以上思路,我们的代码逻辑如下

FFT
multiply
IFFT
f(x)、g(x)
F(x)、G(x)
H(x)
answer
#include <iostream>
#include <vector>
#include <cmath>
#include <complex>

using namespace std;

const double PI = acos(-1.0);

// 递归实现FFT
void fft(vector<complex<double>>& a, bool inv) {
    int n = a.size();
    if (n == 1) {
        return;
    }
    
    //分治
    vector<complex<double>> a0(n / 2), a1(n / 2);
    for (int i = 0, j = 0; i < n; i += 2, j++) {
        a0[j] = a[i];
        a1[j] = a[i + 1];
    }
    fft(a0, inv);
    fft(a1, inv);
    
    //FFT
    double angle = 2 * PI / n * (inv ? -1 : 1);
    complex<double> w(1), wn(cos(angle), sin(angle));
    for (int i = 0; i < n / 2; i++) {
        a[i] = a0[i] + w * a1[i];
        a[i + n / 2] = a0[i] - w * a1[i];
        w *= wn;
    }
}

// FFT乘法
vector<int> multiply(vector<int> a, vector<int> b) {
    int n = 1;
    // 将多项式的项数变为2的整数次幂
    while (n < a.size() + b.size()) {
        n *= 2;
    }
    a.resize(n), b.resize(n);
    
    vector<complex<double>> c(n), d(n);
    for (int i = 0; i < n; i++) {
        c[i] = complex<double>(a[i], 0);
        d[i] = complex<double>(b[i], 0);
    }
    
    // 求原多项式的FFT
    fft(c, false), fft(d, false);
    for (int i = 0; i < n; i++) {
        c[i] *= d[i];
    }
    
    // 求乘法结果的IFFT
    fft(c, true);
    
    // 将IFFT中与逆矩阵相差的1/n乘进去
    vector<int> res(n);
    for (int i = 0; i < n; i++) {
        res[i] = (int)(c[i].real() / n + 0.5);
    }
    
    // 处理进位
    int carry = 0;
    for (int i = 0; i< n; i++) {
    	res[i] += carry;
    	carry = res[i] / 10;
    	res[i] %= 10;
	}
    
    // 去高位的0
	while (res.size() > 1 && res.back() == 0) {
    	res.pop_back();
	}
	return res;
}

// 将大整数字符串转换为vector<int>
vector<int> to_vector(string s) {
	vector<int> res;
	for (int i = s.size() - 1; i >= 0; i--) {
		res.push_back(s[i] - '0');
	}
	return res;
}

// 将vector<int>转换为大整数字符串
string to_string(vector<int> a) {
	string res;
	for (int i = a.size() - 1; i >= 0; i--) {
		res += to_string(a[i]);
	}
	return res;
}

int main() {
	string s1, s2;
	cin >> s1 >> s2;
	vector<int> a = to_vector(s1), b = to_vector(s2);
	vector<int> c = multiply(a, b);
	cout << to_string(c) << endl;
	return 0;
}
  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值