FFT学习笔记(快速傅里叶变换)

本文介绍了快速傅里叶变换(FFT)的基本原理和应用,包括如何通过FFT加速多项式乘法。FFT可以将原本需要平方时间复杂度的多项式乘法降低到对数时间复杂度,点值表示法与系数表示法的转换是关键步骤。此外,还讨论了离散傅里叶变换(DFT)及其逆变换IDFT,以及它们在多项式运算中的作用。文章通过递归和迭代两种方式展示了FFT的实现,并给出了相关例题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

用途

快速傅里叶变换(Fast Fourier Transformation,简称FFT) 一般用来加速多项式乘法。求两个 n n n次多项式相乘,朴素算法需要 O ( n 2 ) O(n^2) O(n2),但FFT只需要 O ( n log ⁡ n ) O(n\log n) O(nlogn)就能解决。


多项式

系数表示法

一个 n n n n + 1 n+1 n+1项多项式可表示为
f ( x ) = a 0 + a 1 x + a 2 x 2 + ⋯ + a n x n f(x)=a_0+a_1x+a_2x^2+\cdots+a_nx^n f(x)=a0+a1x+a2x2++anxn

这就是多项式的系数表示法


点值表示法

n + 1 n+1 n+1个不同的 x x x值代入 f ( x ) f(x) f(x),可以得到 n + 1 n+1 n+1个对应的 f ( x ) f(x) f(x)值。这 n n n ( x k , f ( x k ) ) (x_k,f(x_k)) (xk,f(xk))可以唯一确定一个 n n n次多项式。

为什么呢?可以将 n + 1 n+1 n+1个式子联立起来得到一个 n + 1 n+1 n+1元一次方程组,这个方程组有唯一解。

n + 1 n+1 n+1个点表示一个 n n n次函数,称为点值表示法


系数转为点值

对于一个 n n n次多项式,在已知系数的情况下,将其转为点值表示法,称为DFT(离散傅里叶变换)

使用秦九韶算法,即可 O ( n ) O(n) O(n)转换一个 x x x值,那么求 n n n个点的时间复杂度为 O ( n 2 ) O(n^2) O(n2)


点值转为系数

对于一个由点值表示法表示的 n n n次多项式,将其转化为系数表示法,称为IDFT(离散傅里叶逆变换)

用高斯消元需要 O ( n 3 ) O(n^3) O(n3),用拉格朗日插值法可以达到 O ( n 2 ) O(n^2) O(n2)


多项式乘法

对于用系数表示的 n n n次多项式 f ( x ) f(x) f(x) g ( x ) g(x) g(x),求两式相乘后的多项式,时间复杂度为 O ( n 2 ) O(n^2) O(n2)

用点值表示的呢?

对于 f ( x ) f(x) f(x) g ( x ) g(x) g(x),我们各取 2 n + 1 2n+1 2n+1个点。

( x 0 , f ( x 0 ) ) , ( x 1 , f ( x 1 ) ) , … , ( x 2 n , f ( x 2 n ) ) ( x 0 , g ( x 0 ) ) , ( x 1 , g ( x 1 ) ) , … , ( x 2 n , g ( x 2 n ) ) (x_0,f(x_0)),(x_1,f(x_1)),\dots ,(x_{2n},f(x_{2n}))\\ \qquad \\(x_0,g(x_0)),(x_1,g(x_1)),\dots ,(x_{2n},g(x_{2n})) (x0,f(x0)),(x1,f(x1)),,(x2n,f(x2n))(x0,g(x0)),(x1,g(x1)),,(x2n,g(x2n))

上下相乘得到

( x 0 , f ( x 0 ) × g ( x 0 ) ) , ( x 1 , f ( x 1 ) × g ( x 1 ) ) , … , ( x 2 n , f ( x 2 n ) × g ( x 2 n ) ) (x_0,f(x_0)\times g(x_0)),(x_1,f(x_1)\times g(x_1)),\dots ,(x_{2n},f(x_{2n})\times g(x_{2n})) (x0,f(x0)×g(x0)),(x1,f(x1)×g(x1)),,(x2n,f(x2n)×g(x2n))

显然这 2 n + 1 2n+1 2n+1个点唯一地确定了一个 2 n 2n 2n次函数,这 2 n + 1 2n+1 2n+1个点就是 f ( x ) f(x) f(x) g ( x ) g(x) g(x)的乘积的点值表示法的形式,所以用点值表示法只需要 O ( n ) O(n) O(n)

那如果将系数表示法转换为点值表示法相乘,再转回系数表示法,会不会更快呢?

用朴素算法是不会的,因为DFTIDFT 的时间复杂度都为 O ( n 2 ) O(n^2) O(n2),所以最终的时间复杂度为 O ( n 2 ) O(n^2) O(n2),和直接相乘一样。

有没有其他办法呢?那就要用到FFT 了。


快速傅里叶变换

复数

复数可以写成 a + b i a+bi a+bi的形式。在复平面上为点 ( a , b ) (a,b) (a,b)

x n = 1 x^n=1 xn=1 n n n个根,令 ω n \omega_n ωn表示 x n = 1 x^n=1 xn=1的单位复数根,则 ω n = e 2 π i / n \omega_n=e^{2\pi i/n} ωn=e2πi/n,则方程其他的根就是 ω n \omega_n ωn的若干次幂。

欧拉公式可得
e i θ = cos ⁡ θ + i sin ⁡ θ e^{i\theta}=\cos \theta+i\sin \theta eiθ=cosθ+isinθ

所以我们可以通过三角函数来求 e 2 π i / n e^{2\pi i/n} e2πi/n的实部和虚部。也由此可得, x n = 1 x^n=1 xn=1的根都均匀分布在复平面上以原点为圆心的单位圆的圆周上。

单位复数根有以下性质:

1. 消去引理

对于任意整数 n ≥ 0 , k ≥ 0 , d > 0 n\geq 0,k\geq 0,d>0 n0,k0,d>0,都有 ω d n d k = ω n k \omega_{dn}^{dk}=\omega_n^k ωdndk=ωnk

证明: ω d n d k = ( e 2 π i / d n ) d k = ( e 2 π i / n ) k = ω n k \omega_{dn}^{dk}=(e^{2\pi i/dn})^{dk}=(e^{2\pi i/n})^k=\omega_n^k ωdndk=(e2πi/dn)dk=(e2πi/n)k=ωnk

2. 折半引理

对于偶数 n ≥ 0 n\geq 0 n0和整数 k ≥ 0 k\geq 0 k0 ( ω n k + n / 2 ) 2 = ω n / 2 k (\omega_{n}^{k+n/2})^2=\omega_{n/2}^{k} (ωnk+n/2)2=ωn/2k

证明: ( ω n k + n / 2 ) 2 = ω n 2 k + n = ω n 2 k = ω n / 2 k (\omega_{n}^{k+n/2})^2=\omega_{n}^{2k+n}=\omega_{n}^{2k}=\omega_{n/2}^{k} (ωnk+n/2)2=ωn2k+n=ωn2k=ωn/2k

3. 求和引理

对于任意整数 n ≥ 1 n\geq 1 n1和不能被 n n n整除的整数 k k k,有 ∑ j = 0 n − 1 ( ω n k ) j = 0 \sum\limits_{j=0}^{n-1}(\omega_n^k)^j=0 j=0n1(ωnk)j=0

证明: ∑ j = 0 n − 1 ( ω n k ) j = 1 − ( ω n k ) n 1 − ω n k = 0 1 − ω n k = 0 \sum\limits_{j=0}^{n-1}(\omega_n^k)^j=\dfrac{1-(\omega_n^k)^n}{1-\omega_n^k}=\dfrac{0}{1-\omega_n^k}=0 j=0n1(ωnk)j=1ωnk1(ωnk)n=1ωnk0=0


FFT

我们考虑将 ω n 0 , ω n 1 , ω n 2 , … , ω n n − 1 \omega_n^0,\omega_n^1,\omega_n^2,\dots,\omega_n^{n-1} ωn0,ωn1,ωn2,,ωnn1代入 n − 1 n-1 n1次多项式求值。
(下文将 n n n视作2的幂,次数大于等于 n n n的项可看作系数为0)

有多项式
A ( x ) = a 0 + a 1 x + a 2 x 2 + ⋯ + a n − 1 x n − 1 A(x)=a_0+a_1x+a_2x^2+\cdots+a_{n-1}x^{n-1} A(x)=a0+a1x+a2x2++an1xn1

A ( x ) A(x) A(x)根据下标奇偶性分开
A ( x ) = ( a 0 + a 2 x 2 + ⋯ + a n − 2 x n − 2 ) + ( a 1 x + a 3 x 3 + ⋯ + a n − 1 x n − 1 ) = ( a 0 + a 2 x 2 + ⋯ + a n − 2 x n − 2 ) + x ( a 1 + a 3 x 2 + ⋯ + a n − 1 x n − 2 ) A(x)=(a_0+a_2x^2+\cdots+a_{n-2}x^{n-2})+(a_1x+a_3x^3+\cdots+a_{n-1}x^{n-1}) \\ \qquad \\ =(a_0+a_2x^2+\cdots+a_{n-2}x^{n-2})+x(a_1+a_3x^2+\cdots+a_{n-1}x^{n-2}) A(x)=(a0+a2x2++an2xn2)+(a1x+a3x3++an1xn1)=(a0+a2x2++an2xn2)+x(a1+a3x2++an1xn2)

A 1 ( x ) = a 0 + a 2 x + ⋯ + a n − 2 x n / 2 − 1 A 2 ( x ) = a 1 + a 3 x + ⋯ + a n − 1 x n / 2 − 1 A_1(x)=a_0+a_2x+\cdots+a_{n-2}x^{n/2-1}\\ A_2(x)=a_1+a_3x+\cdots+a_{n-1}x^{n/2-1} A1(x)=a0+a2x++an2xn/21A2(x)=a1+a3x++an1xn/21

可得
A ( x ) = A 1 ( x 2 ) + x A 2 ( x 2 ) A(x)=A_1(x^2)+xA_2(x^2) A(x)=A1(x2)+xA2(x2)

0 ≤ k < n / 2 0\leq k<n/2 0k<n/2,将 x = ω n k x=\omega_n^k x=ωnk代入 A ( x ) A(x) A(x),得
A ( ω n k ) = A 1 ( ( ω n k ) 2 ) + ω n k A 2 ( ( ω n k ) 2 ) A 1 ( ω n 2 k ) + ω n k A 2 ( ω n 2 k ) = A 1 ( ω n / 2 k ) + ω n k A 2 ( ω n / 2 k ) A(\omega_n^k)=A_1((\omega_n^k)^2)+\omega_n^kA_2((\omega_n^k)^2)\\ \qquad \\A_1(\omega_n^{2k})+\omega_n^kA_2(\omega_n^{2k})=A_1(\omega_{n/2}^k)+\omega_n^kA_2(\omega_{n/2}^k) A(ωnk)=A1((ωnk)2)+ωnkA2((ωnk)2)A1(ωn2k)+ωnkA2(ωn2k)=A1(ωn/2k)+ωnkA2(ωn/2k)

x = ω n k + n / 2 x=\omega_n^{k+n/2} x=ωnk+n/2代入 A ( x ) A(x) A(x),得
A ( ω n k + n / 2 ) = A 1 ( ( ω n k + n / 2 ) 2 ) + ω n k + n / 2 A 2 ( ( ω n k + n / 2 ) 2 ) A 1 ( ω n 2 k ) + ω n k ⋅ ω n n / 2 A 2 ( ω n 2 k ) = A 1 ( ω n / 2 k ) − ω n k A 2 ( ω n / 2 k ) A(\omega_n^{k+n/2})=A_1((\omega_n^{k+n/2})^2)+\omega_n^{k+n/2}A_2((\omega_n^{k+n/2})^2)\\ \qquad \\A_1(\omega_n^{2k})+\omega_n^k\cdot \omega_n^{n/2} A_2(\omega_n^{2k})=A_1(\omega_{n/2}^k)-\omega_n^kA_2(\omega_{n/2}^k) A(ωnk+n/2)=A1((ωnk+n/2)2)+ωnk+n/2A2((ωnk+n/2)2)A1(ωn2k)+ωnkωnn/2A2(ωn2k)=A1(ωn/2k)ωnkA2(ωn/2k)

我们可以发现, A ( ω n k ) A(\omega_n^k) A(ωnk) A ( ω n k + n / 2 ) A(\omega_n^{k+n/2}) A(ωnk+n/2) A 1 ( x ) A_1(x) A1(x) A 2 ( x ) A_2(x) A2(x)表示之后只有符号不同。
A ( ω n k ) = A 1 ( ω n / 2 k ) + ω n k A 2 ( ω n / 2 k ) A ( ω n k + n / 2 ) = A 1 ( ω n / 2 k ) − ω n k A 2 ( ω n / 2 k ) A(\omega_n^k)=A_1(\omega_{n/2}^k)+\omega_n^kA_2(\omega_{n/2}^k) \\ \qquad \\ A(\omega_n^{k+n/2})=A_1(\omega_{n/2}^k)-\omega_n^kA_2(\omega_{n/2}^k) A(ωnk)=A1(ωn/2k)+ωnkA2(ωn/2k)A(ωnk+n/2)=A1(ωn/2k)ωnkA2(ωn/2k)

于是,当我们求出 A 1 ( ω n / 2 k ) A_1(\omega_{n/2}^k) A1(ωn/2k) A 2 ( ω n / 2 k ) A_2(\omega_{n/2}^k) A2(ωn/2k)之后,就能求出 A ( ω n k ) A(\omega_n^k) A(ωnk) A ( ω n k + n / 2 ) A(\omega_n^{k+n/2}) A(ωnk+n/2)的值。

那么问题就转化为求 A 1 ( x ) A_1(x) A1(x) A 2 ( x ) A_2(x) A2(x) ω n / 2 0 , ω n / 2 1 , … , ω n / 2 n / 2 − 1 \omega_{n/2}^0,\omega_{n/2}^1,\dots,\omega_{n/2}^{n/2-1} ωn/20,ωn/21,,ωn/2n/21处的值,可以用分治来解决问题。

以下是递归写法,用于理解,后面会介绍更优的迭代写法。

code

const double pi=acos(-1.0);
int a1[N],a2[N],y[N];
int fft(int *a,int l){
	if(l==1) return a;
	cp wn=(cp){cos(2*pi/l),sin(2*pi/l)};
	cp w=(cp){1,0};
	for(int i=0;i<l/2;i++){
		a1[i]=a[i*2];
		a2[i]=a[i*2+1];
	}
	int *y0=fft(a1,l/2);
	int *y1=fft(a2,l/2);
	for(int i=0;i<l/2;i++){
		y[i]=y0[i]+y1[i]*w;
		y[i+l/2]=y0[i]-y1[i]*w;
		w=w*wn;
	}
	return y;
}

其结构与 c d q cdq cdq分治相似,相当于有 log ⁡ n \log n logn层,每层都是 n n n个位置,所以时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)


IFFT

我们已经可以在 O ( n log ⁡ n ) O(n\log n) O(nlogn)的时间复杂度下将系数表示法转化为点值表示法。接下来,我们要考虑如何将两个多项式的乘积的点值表示法再变回系数表示法。

对于前面的FFT,其过程可以用以下矩阵表示:
[   1 1 1 ⋯ 1     1 ω n 1 ω n 2 ⋯ ω n n − 1     1 ω n 2 ω n 4 ⋯ ω n 2 ( n − 1 )     ⋮ ⋮ ⋮ ⋱ ⋮     1 ω n n − 1 ω n 2 ( n − 1 ) ⋯ ω n ( n − 1 ) ( n − 1 )   ] [   a 0     a 1     a 2     ⋮     a n − 1   ] = [   y 0     y 1     y 2     ⋮     y n − 1   ] \left[ \begin{matrix} \ 1 & 1 & 1 & \cdots & 1 \ \\ \ 1 & \omega_n^1 & \omega_n^2 & \cdots & \omega_n^{n-1} \ \\ \ 1 & \omega_n^2 & \omega_n^4 & \cdots & \omega_n^{2(n-1)} \ \\ \ \vdots & \vdots & \vdots & \ddots & \vdots \ \\ \ 1 & \omega_n^{n-1} & \omega_n^{2(n-1)} & \cdots & \omega_n^{(n-1)(n-1)} \ \end{matrix} \right] \left[ \begin{matrix} \ a_0 \ \\ \ a_1 \ \\ \ a_2 \ \\ \ \vdots \ \\ \ a_{n-1} \ \end{matrix} \right] =\left[ \begin{matrix} \ y_0 \ \\ \ y_1 \ \\ \ y_2 \ \\ \ \vdots \ \\ \ y_{n-1} \ \end{matrix} \right]  1 1 1  11ωn1ωn2ωnn11ωn2ωn4ωn2(n1)1 ωnn1 ωn2(n1)  ωn(n1)(n1)   a0  a1  a2    an1  =  y0  y1  y2    yn1 

左边的矩阵是范德蒙德矩阵, V i , j = ω n i j V_{i,j}=\omega_n^{ij} Vi,j=ωnij,可以构造出它的逆矩阵 V − 1 V^{-1} V1,其中 ( V − 1 ) i , j = ω n − i j n (V^{-1})_{i,j}=\dfrac{\omega_n^{-ij}}{n} (V1)i,j=nωnij

[   1 / n 1 / n 1 / n ⋯ 1 / n     1 / n ω n − 1 / n ω n − 2 / n ⋯ ω n − ( n − 1 ) / n     1 / n ω n − 2 / n ω n − 4 / n ⋯ ω n − 2 ( n − 1 ) / n     ⋮ ⋮ ⋮ ⋱ ⋮     1 / n ω n − ( n − 1 ) / n ω n − 2 ( n − 1 ) / n ⋯ ω n − ( n − 1 ) ( n − 1 ) / n   ] [   y 0     y 1     y 2     ⋮     y n − 1   ] = [   a 0     a 1     a 2     ⋮     a n − 1   ] \left[ \begin{matrix} \ 1/n & 1/n & 1/n & \cdots & 1/n \ \\ \ 1/n & \omega_n^{-1}/n & \omega_n^{-2}/n & \cdots & \omega_n^{-(n-1)}/n \ \\ \ 1/n & \omega_n^{-2}/n & \omega_n^{-4}/n & \cdots & \omega_n^{-2(n-1)}/n \ \\ \ \vdots & \vdots & \vdots & \ddots & \vdots \ \\ \ 1/n & \omega_n^{-(n-1)}/n & \omega_n^{-2(n-1)}/n & \cdots & \omega_n^{-(n-1)(n-1)}/n \ \end{matrix} \right] \left[ \begin{matrix} \ y_0 \ \\ \ y_1 \ \\ \ y_2 \ \\ \ \vdots \ \\ \ y_{n-1} \ \end{matrix} \right] =\left[ \begin{matrix} \ a_0 \ \\ \ a_1 \ \\ \ a_2 \ \\ \ \vdots \ \\ \ a_{n-1} \ \end{matrix} \right]  1/n 1/n 1/n  1/n1/nωn1/nωn2/nωn(n1)/n1/nωn2/nωn4/nωn2(n1)/n1/n ωn(n1)/n ωn2(n1)/n  ωn(n1)(n1)/n   y0  y1  y2    yn1  =  a0  a1  a2    an1 

相当于在做FFT时在原来的基础上将 ω n \omega_n ωn变成 ω n − 1 \omega_n^{-1} ωn1,然后对结果的每一项除以 n n n即可,时间复杂度也为 O ( n log ⁡ n ) O(n\log n) O(nlogn)


蝴蝶变换

由上文可得知,可以将 A ( x ) A(x) A(x)拆成 A 0 ( x ) A_0(x) A0(x) A 1 ( x ) A_1(x) A1(x)来求值。

但是用上述的递归代码跑的速度较慢,所以我们考虑用更高效的算法。

观察分治的过程,看看有什么规律。

在这里插入图片描述

我们发现,最底层的系数排列顺序,正好是其二进制的镜面翻转。

如第 1 1 1个, 1 1 1的二进制是 001 001 001,翻转后是 100 100 100,即十进制的 4 4 4,所以从第 0 0 0个位置开始的第一个位置是 a 4 a_4 a4

为什么呢?因为在每次分治的时候,我们都将低位为 0 0 0的放在左,为 1 1 1的放在右。但如果将每个数的下标镜面翻转,我们就发现这是在将 a a a序列按二进制位从高到低来分,最后自然就是从小到大。再镜面翻转回来,即可得到原来的系数。这个变换称为蝴蝶变换

代码中的 j j j i i i翻转后的值, j j j在这个循环中其实就是在模拟 i i i进位的过程。

code

void ch(cp *a,int l){
	for(int i=1,j=l/2,k;i<l-1;i++){
		if(i<j) swap(a[i],a[j]);
		k=l/2;
		while(j>=k){
			j-=k;k>>=1;
		}
		j+=k;
	}
}

以下是FFT的迭代写法。

void fft(cp *a,int l){
	for(int i=2;i<=l;i<<=1){
		wn=(cp){cos(2*pi/i),sin(2*pi/i)};
		for(int j=0;j<l;j+=i){
			w=(cp){1,0};
			for(int k=j;k<j+i/2;k++,w=w*wn){
				cp t=a[k],u=w*a[k+i/2];
				a[k]=t+u;
				a[k+i/2]=t-u;
			}
		}
	}
}

总结

解决多项式乘法的过程:

  • FFT将系数表示法转化为点值表示法
  • 用点值表示法来求多项式乘法
  • IFFT将点值表示法再变回系数表示法

这样即可用 O ( n log ⁡ n ) O(n\log n) O(nlogn)的时间复杂度求出多项式的乘积。


例题

多项式乘法(FFT)

模板题

l l l表示最后多项式的次数,则 n n n需满足 n ≥ l n\geq l nl n n n 2 2 2的若干次幂。

code

#include<bits/stdc++.h>
using namespace std;
const int N=5000005;
const double pi=acos(-1.0);
char s1[N],s2[N];
long long ans[N];
struct cp{
	double a,b;
	cp operator +(const cp ax)const{
		return (cp){a+ax.a,b+ax.b};
	}
	cp operator -(const cp ax)const{
		return (cp){a-ax.a,b-ax.b};
	}
	cp operator *(const cp ax)const{
		return (cp){a*ax.a-b*ax.b,b*ax.a+a*ax.b};
	}
}w,wn,a1[N],a2[N];
void ch(cp *a,int l){
	for(int i=1,j=l/2,k;i<l-1;i++){
		if(i<j) swap(a[i],a[j]);
		k=l/2;
		while(j>=k){
			j-=k;k>>=1;
		}
		j+=k;
	}
}
void fft(cp *a,int l,int fl){
	for(int i=2;i<=l;i<<=1){
		wn=(cp){cos(fl*2*pi/i),sin(fl*2*pi/i)};
		for(int j=0;j<l;j+=i){
			w=(cp){1,0};
			for(int k=j;k<j+i/2;k++,w=w*wn){
				cp t=a[k],u=w*a[k+i/2];
				a[k]=t+u;
				a[k+i/2]=t-u;
			}
		}
	}
}
int main()
{
	int n,l,l1,l2,x;
	scanf("%d%d",&l1,&l2);++l1;++l2;
	l=l1+l2;n=1;
	while(n<l) n<<=1;
	for(int i=0;i<l1;i++) scanf("%d",&x),a1[l1-i-1]=(cp){(double)(x),0};
	for(int i=l1;i<n;i++) a1[i]=(cp){0,0};
	for(int i=0;i<l2;i++) scanf("%d",&x),a2[l2-i-1]=(cp){(double)(x),0};
	for(int i=l2;i<n;i++) a2[i]=(cp){0,0};
	ch(a1,n);ch(a2,n);
	fft(a1,n,1);
	fft(a2,n,1);
	for(int i=0;i<n;i++) a2[i]=a1[i]*a2[i];
	ch(a2,n);
	fft(a2,n,-1);
	for(int i=0;i<l1+l2-1;i++){
		ans[i]=(long long)(a2[i].a/n+0.5);
	}
	while(ans[l]==0&&l>0) --l;
	for(int i=l;i>=0;i--) printf("%lld ",ans[i]);
	return 0;
}

A*B Problem 升级版(FFT 快速傅里叶变换)

a a a b b b看作两个多项式 f ( x ) f(x) f(x) g ( x ) g(x) g(x),求多项式的乘积,再代入 x = 10 x=10 x=10即可。

code

#include<bits/stdc++.h>
using namespace std;
const double pi=acos(-1.0);
char s1[5000005],s2[5000005];
long long ans[5000005];
struct cp{
	double a,b;
	cp operator +(const cp ax)const{
		return (cp){a+ax.a,b+ax.b};
	}
	cp operator -(const cp ax)const{
		return (cp){a-ax.a,b-ax.b};
	}
	cp operator *(const cp ax)const{
		return (cp){a*ax.a-b*ax.b,b*ax.a+a*ax.b};
	}
}w,wn,a1[5000005],a2[5000005];
void ch(cp *a,int l){
	for(int i=1,j=l/2,k;i<l-1;i++){
		if(i<j) swap(a[i],a[j]);
		k=l/2;
		while(j>=k){
			j-=k;k>>=1;
		}
		j+=k;
	}
}
void fft(cp *a,int l,int fl){
	for(int i=2;i<=l;i<<=1){
		wn=(cp){cos(fl*2*pi/i),sin(fl*2*pi/i)};
		for(int j=0;j<l;j+=i){
			w=(cp){1,0};
			for(int k=j;k<j+i/2;k++,w=w*wn){
				cp t=a[k],u=w*a[k+i/2];
				a[k]=t+u;
				a[k+i/2]=t-u;
			}
		}
	}
}
int main()
{
	int n,l,l1,l2;
	scanf("%s%s",s1,s2);
	l1=strlen(s1);
	l2=strlen(s2);
	l=l1+l2;n=1;
	while(n<l) n<<=1;
	for(int i=0;i<l1;i++) a1[l1-i-1]=(cp){(double)(s1[i]-'0'),0};
	for(int i=l1;i<n;i++) a1[i]=(cp){0,0};
	for(int i=0;i<l2;i++) a2[l2-i-1]=(cp){(double)(s2[i]-'0'),0};
	for(int i=l2;i<n;i++) a2[i]=(cp){0,0};
	ch(a1,n);ch(a2,n);
	fft(a1,n,1);
	fft(a2,n,1);
	for(int i=0;i<n;i++) a2[i]=a1[i]*a2[i];
	ch(a2,n);
	fft(a2,n,-1);
	for(int i=0;i<l1+l2-1;i++){
		ans[i]+=(long long)(a2[i].a/n+0.5);
		ans[i+1]+=ans[i]/10;
		ans[i]%=10;
	}
	while(ans[l]==0&&l>0) --l;
	for(int i=l;i>=0;i--) printf("%lld",ans[i]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值