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

news2024/9/30 1:31:33

用途

快速傅里叶变换(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)\cdot g(x_0)),(x_1,f(x_1)\cdot g(x_1)),\dots ,(x_{2n},f(x_{2n})\cdot 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/n),sin(2*pi/n)};
	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,n/2);
	int *y1=fft(a2,n/2);
	for(int i=0;i<n/2;i++){
		y[i]=y0[i]+y1[i]*w;
		y[i+n/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 & \vdots & \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 1 1 ⋯ 1     1 ω n − 1 / n ω n − 2 / n ⋯ ω n − ( n − 1 ) / n     1 ω n − 2 / n ω n − 4 / n ⋯ ω n − 2 ( n − 1 ) / n     ⋮ ⋮ ⋮ ⋮ ⋮     1 ω 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 & 1 & 1 & \cdots & 1 \ \\ \ 1 & \omega_n^{-1}/n & \omega_n^{-2}/n & \cdots & \omega_n^{-(n-1)}/n \ \\ \ 1 & \omega_n^{-2}/n & \omega_n^{-4}/n & \cdots & \omega_n^{-2(n-1)}/n \ \\ \ \vdots & \vdots & \vdots & \vdots & \vdots \ \\ \ 1 & \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 1 1  11ωn1/nωn2/nωn(n1)/n1ωn2/nωn4/nωn2(n1)/n1 ω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序列按二进制位从高到低来分,最后自然就是从小到大。再镜面翻转回来,即可得到原来的系数。这个变换称为蝴蝶变换

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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/96383.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于java的扫雷游戏的设计-计算机毕业设计

项目介绍 扫雷游戏的基本功能&#xff1a;点击鼠标左键于未知区域,如果未知区域有雷,游戏停止,显示所有的地雷。如果没雷,则显示周围雷数,如果周围没雷,则再查看周围八个区域是否有雷直到有雷为止并显示,玩家需要尽快找出雷区中的所有不是地雷的方块&#xff0c;而不许踩到地雷…

jsp+ssh+mysql实现的Java web学生考勤管理系统源码附带视频指导运行教程

今天给大家演示的是一款由jspsshmysql实现的Java web学生考勤管理系统&#xff0c;其中struts版本是struts2。本系统实现了管理员、学生、教师三个角色的功能&#xff0c;其中管理员可以管理基本信息&#xff0c;如班级信息、课程信息、用户信息、课程表等。教师可以管理自己班…

mongodb实现请求日志存储

引言 最近学习了mongodb&#xff0c;想实际应用到项目中&#xff0c;就先简单实现了一个存储请求日志的功能&#xff1b; 为什么使用mongodb存储日志&#xff0c;主要是因为日志数据量大、低价值、写入频繁&#xff0c;并且对事务要求不高&#xff0c;使用传统的关系型数据库…

Java强软弱虚引用和ThreadLocal工作原理(二)

1. 前言 读本篇文章之前&#xff0c;请移步到上一篇文章Java强软弱虚引用和ThreadLocal工作原理&#xff08;一&#xff09;_broadview_java的博客-CSDN博客 我们继续来讲一下java的强软弱引用在Android开发中的使用&#xff0c;并深入理解一下ThreadLocal的原理 2. 强软弱引…

Pro3:js实现放大镜效果

在我们平时见到很多购物网站都会有放大镜效果的出现&#xff0c;当我们将鼠标放在一个商品图片的上面&#xff0c;就会在旁边出现对应的放大效果。 实现步骤 实现原理是非常简单的&#xff0c;实际上是两张图片&#xff0c;一张原图和一张更大尺寸的图片。一开始通过css样式…

MessageFormat的具体使用(格式化消息)

文章目录1. 前言2. 先说结论3. 在结论上补充其他更加特殊情况1. 数字类型可以使用#字符来确认精度2. 数组类型转化需要注意3. 输出特殊字符4. 如何判断一个String是否有替换位4. 粗略原理1. 前言 在工作中发现接口的返回报文&#xff0c;大部分公司通常都会封装一层&#xff0c…

2022-LCLR-DIFFDOCK: DIFFUSION STEPS, TWISTS, AND TURNS FOR MOLECULAR DOCKING

2022-LCLR-DIFFDOCK: DIFFUSION STEPS, TWISTS, AND TURNS FOR MOLECULAR DOCKING Paper: https://arxiv.org/abs/2210.01776 Code: https://github.com/gcorso/DiffDock 预测小分子配体与蛋白质的结合结构(称为分子对接)是药物设计的关键。最近的深度学习方法将对接视为一个回…

GB/T 20984-2022《信息安全技术 信息安全风险评估方法》解读

前言 近年来&#xff0c;信息安全风险评估工作逐步在国家基础信息网络及重要行业信息系统中普遍推行&#xff0c;信息安全风险评估是信息安全保障工作的基础和重要环节&#xff0c;日前&#xff0c; GB/T 20984-2022 《信息安全技术 信息安全风险评估方法》发布&#xff0c;将…

oracle学习篇(四)

oracle学习篇(四&#xff09; 1 PL/SQL异常处理 1.1 预定义异常 1.1.1 内容 oracle里面已经存在的异常 如果是自定义异常,一般写的编号是20000-20999之间1.1.2 处理异常语法 exceptionwhen 异常类型1 then输出异常类型信息1;when 异常信息2 then输出异常类型信息2;--以上都…

MR案例:学生排序(单字段排序、多字段排序)

文章目录一、提出任务二、完成任务&#xff08;一&#xff09;准备数据1、在虚拟机上创建文本文件2、上传文件到HDFS指定目录&#xff08;二&#xff09;实现步骤1、创建Maven项目2、添加相关依赖3、创建日志属性文件4、创建学生实体类5、创建学生映射器类5、创建学生归并器类6…

JS中操作<select>标签选的值

JS中操作<select>标签选的值 <select>标签是一种表单控件&#xff0c;用来创建下拉列表。在<select> 标签内可用 <option> 标签定义下拉列表中的可用选项。下面给出一个基本下拉列表示例&#xff1a; <!DOCTYPE html> <html lang"zh&q…

Codeforces Round #838 (Div. 2)

A. Divide and Conquer 题目链接&#xff1a;Problem - A - Codeforces 样例输入&#xff1a; 4 4 1 1 1 1 2 7 4 3 1 2 4 1 15样例输出&#xff1a; 0 2 1 4题意&#xff1a;一个数组是好的当且仅当所有的元素和是一个偶数&#xff0c;现在给我们一个初始数组&#xff0c;我…

Android---组件化

1、单体应用 所有代码写在一个工程里。不同业务写到各自模块&#xff0c;以包名来区分。 弊端 1、无论包名做的再好&#xff0c;随着项目扩大&#xff0c;项目失去层次感&#xff0c;接受吃力。 2、报名作为约束&#xff0c;太弱了。一不注意就会出现不同业务之间之间相互调…

【算法数据结构专题】「限流算法专项」带你认识常用的限流算法的技术指南(分析篇)

限流 限流的目的是通过对并发访问/请求进行限速&#xff0c;或者对一个时间窗口内的请求进行限速来保护系统&#xff0c;一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理 限流一词常用于计算机网络之中&#xff0c;定义如下&#xff1a; In computer networks, rate l…

接口测试(七)—— 参数化、数据库操作类封、接口自动化框架

目录 一、接口自动化测试框架 1、目录结构 二、封装iHRM登录 1、普通方式实现 2、登录接口对象层 3、登录接口测试用例层 4、封装断言方法 三、参数化 1、回顾UnitTest参数化 1.1 原始案例 1.2 参数化实现 1.3 从json文件读取 2、登录接口参数化 2.1 组织数据文…

java8流操作之不常用但是很好用的隐藏api

前言 1、一些普通的方式就不再多说了&#xff0c;这里主要说一些不常用的&#xff0c;但是作用很大的api方式 2、如果想要细致了解可以参考 JAVA8的流操作&#xff0c;十分推荐 一、flatMap 1、这个api主要是用来推平流的&#xff0c;和map不一致&#xff0c;map是对象到对…

Python基础(十六):函数的初步认识

文章目录 函数的初步认识 一、函数的作用 二、函数的使用步骤 1、定义函数 2、调用函数 3、快速体验 三、函数的参数作用 四、函数的返回值作用 1、应用 五、函数的说明文档 1、语法 2、快速体验 3、函数嵌套调用 七、函数应用 1、打印图形 2、函数计算 八、总…

还在为多张Excel汇总统计发愁?Python 秒处理真香!

为什么越来越多的非程序员白领都开始学习 Python &#xff1f;他们可能并不是想要学习 Python 去爬取一些网站从而获得酷酷的成就感&#xff0c;而是工作中遇到好多数据分析处理的问题&#xff0c;用 Python 就可以简单高效地解决。本文就通过一个实际的例子来给大家展示一下 P…

新手传奇gm架设要学会的几个修改技巧

每个传奇gm对于架设一个服务器都有自己独立的看法和想法&#xff0c;一些人之所以会想着要架设一个传奇私服主要原因是自己在其他人的服力玩得不是那么舒心。所以想要按照自己的想法和思路打造一个适合自己的专属服务器进行游戏&#xff0c;其实这两者之间是有必然联系的&#…

毕业三年活得像个废物,转行网络安全,写给像我一样迷茫的人...

首先说说我吧&#xff0c;普通二本非科班商贸专业毕业&#xff0c;三年了&#xff0c;做过电商&#xff0c;做过新媒体&#xff0c;做过业务员&#xff0c;从躺平到摆烂&#xff0c;一开始还挺享受这样的生活的&#xff0c;毕竟每月4千工资&#xff0c;抛出吃住&#xff0c;剩个…