数学板块学习之FFT

本文介绍了FFT(快速傅氏变换),它是离散傅氏变换的快速算法。在信息学竞赛中用于快速求解多项式乘法。文中阐述了系数表示法与点值表示法及其转换,引入单位复根得到傅里叶变换,还介绍了FFT分治思想、蝶形算法等内容。

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

FFT

FFT(Fast Fourier Transformation)是离散傅氏变换(DFT)的快速算法。即为快速傅氏变换。它是根据离散傅氏变换的奇、偶、虚、实等特性,对离散傅立叶变换的算法进行改进获得的。—baidu

信息学竞赛中FFT用于快速求解多项式乘法,也就是卷积

定义两个向量a,b,其中 a = ( a 0 , a 1 , ⋯   , a n − 1 ) , b = ( b 0 , b 1 , ⋯   , b n − 1 ) a=(a_0,a_1,\cdots,a_{n-1}),b=(b_0,b_1,\cdots,b_{n-1}) a=(a0,a1,,an1),b=(b0,b1,,bn1)
向量和 a + b = ( a 0 + b 0 , a 1 + b 1 , ⋯   , a n − 1 + b n − 1 ) a+b=(a_0+b_0,a_1+b_1,\cdots,a_{n-1}+b_{n-1}) a+b=(a0+b0,a1+b1,,an1+bn1)
点积 a ⋅ b = ( a 0 b 0 , a 1 b 1 , ⋯   , a n − 1 b n − 1 ) a·b=(a_0b_0,a_1b_1,\cdots,a_{n-1}b_{n-1}) ab=(a0b0,a1b1,,an1bn1)
卷积 a ∗ b = ( c 0 , c 1 , ⋯   , c 2 n − 2 ) , c k = ∑ i + j = k a i ⋅ = b j a*b=(c_0,c_1,\cdots,c_{2n-2}),c_k=\sum_{i+j=k}{a_i·=b_j} ab=(c0,c1,,c2n2),ck=i+j=kai=bj

系数表示法与点值表示法
给出一个多项式 A ( x ) A(x) A(x)
系数表示法: A ( x ) = a 0 x + a 1 x 1 + ⋯ + a n − 1 x n − 1 = ∑ i = 0 n − 1 a i x i ⇔ A(x)=a_0x+a_1x^1+\cdots+a_{n-1}x^{n-1}=\sum_{i=0}^{n-1}a_ix^i\Leftrightarrow A(x)=a0x+a1x1++an1xn1=i=0n1aixi 系数向量 a = ( a 0 , a 1 , ⋯   , a n − 1 ) a=(a_0,a_1,\cdots,a_{n-1}) a=(a0,a1,,an1)
点值表示法: A ( x ) = { ( x 0 , y 0 ) , ( x 1 , y 1 ) , ⋯   , ( x n − 1 , y n − 1 ) } ( x 0 , x 1 , ⋯   , x n − 1 ) A(x)=\{(x_0,y_0),(x_1,y_1),\cdots,(x_{n-1},y_{n-1})\}(x_0,x_1,\cdots,x_{n-1}) A(x)={(x0,y0),(x1,y1),,(xn1,yn1)}(x0,x1,,xn1)( x i x_i xi互不相同)

转换
系数表示法–>点值表示法
y i = ∑ k = 0 n − 1 c i x i k y_i=\sum_{k=0}^{n-1}c_ix_i^k yi=k=0n1cixik
点值表示法–>系数表示法
f ( x ) = ∑ i = 0 n − 1 y i Π j = ̸ i 0 ≤ j &lt; n ( x − x j ) Π j = ̸ i 0 ≤ j &lt; n ( x i − x j ) f(x)=\sum_{i=0}^{n-1}y_i\cfrac{\Pi_{j=\not i}^{0\leq j &lt;n}(x-x_j)}{\Pi_{j=\not i}^{0\leq j &lt;n}(x_i-x_j)} f(x)=i=0n1yiΠj≠i0j<n(xixj)Πj≠i0j<n(xxj)
由点值转换为系数是拉格朗日插值法

如果多项式 A ( x ) , B ( x ) A(x),B(x) A(x),B(x)(假设两个多项式的次数和小于 n n n,即 d e g A + d e g B &lt; n degA+degB&lt;n degA+degB<n)的点值表示都是在 { x 0 , x 1 , ⋯ &ThinSpace; , x n − 1 } \{x_0,x_1,\cdots,x_{n-1}\} {x0,x1,,xn1}
A ( x ) = { ( x 0 , A ( x 0 ) ) , ( x 1 , A ( x 1 ) ) , ⋯ &ThinSpace; , ( x n − 1 , A ( x n − 1 ) ) } A(x)=\{(x_0,A(x_0)),(x_1,A(x_1)),\cdots,(x_{n-1},A(x_{n-1}))\} A(x)={(x0,A(x0)),(x1,A(x1)),,(xn1,A(xn1))}
B ( x ) = { ( x 0 , B ( x 0 ) ) , ( x 1 , B ( x 1 ) ) , ⋯ &ThinSpace; , ( x n − 1 , B ( x n − 1 ) ) } B(x)=\{(x_0,B(x_0)),(x_1,B(x_1)),\cdots,(x_{n-1},B(x_{n-1}))\} B(x)={(x0,B(x0)),(x1,B(x1)),,(xn1,B(xn1))}
( A ⋅ B ) ( x ) = { ( x 0 , A ( x 0 ) ⋅ B ( x 0 ) ) , ( x 1 , A ( x 1 ) ⋅ B ( x 1 ) ) , ⋯ &ThinSpace; , ( x n − 1 , A ( x n − 1 ) ⋅ B ( x n − 1 ) ) } (A·B)(x)=\{(x_0,A(x_0)·B(x_0)),(x_1,A(x_1)·B(x_1)),\cdots,(x_{n-1},A(x_{n-1})·B(x_{n-1}))\} (AB)(x)={(x0,A(x0)B(x0)),(x1,A(x1)B(x1)),,(xn1,A(xn1)B(xn1))}
( A ⋅ B ) ( x i ) = A ( x i ) ⋅ B ( x i ) (A·B)(x_i)=A(x_i)·B(x_i) (AB)(xi)=A(xi)B(xi)

我们可以通过n+1个点确定两个n阶多项式是否相等:
定理:如果 f ( x ) , g ( x ) f(x),g(x) f(x),g(x) R [ x ] R[x] R[x]的两个多项式,且两个多项式的次数都不大于n,若以 R R R中的n+1个点或更多的点代替 x x x得到的每个 f ( x ) , g ( x ) f(x),g(x) f(x),g(x)都相等,则 f ( x ) = g ( x ) f(x)=g(x) f(x)=g(x)

由该定理知道我们可以通过n个点确定一个n-1阶多项式

引入单位复根
我们知道世界上最完美的公式就是欧拉公式 e i π = − 1 e^{i\pi}=-1 eiπ=1
对于任意实数有 e i x = c o s ( x ) + i s i n ( x ) e^{ix}=cos(x)+isin(x) eix=cos(x)+isin(x)
在这里插入图片描述在这里插入图片描述
我们称满足 ω n = 1 \omega^n=1 ωn=1 ω \omega ω为1的n次复根,其中 ω n k = e i k 2 π n \omega_n^k=e^{\frac{ik2\pi}{n}} ωnk=enik2π,k为从x轴正半轴逆时针旋转的编号,明显k为n时 ω n n = 1 \omega_n^{n}=1 ωnn=1,k为 n 2 \frac{n}{2} 2n ω n n 2 = − 1 \omega_n^{\frac{n}{2}}=-1 ωn2n=1
因此对于 ω n k \omega_n^k ωnk我们只需要求出 ω n 1 \omega_n^1 ωn1就可以得到 ω n k \omega_n^k ωnk,所以我们称 ω n 1 \omega_n^1 ωn1为单位复根,简写为 ω n \omega_n ωn
性质:
ω 2 n 2 k = ω n k \omega_{2n}^{2k}=\omega_n^k ω2n2k=ωnk
ω n k + n 2 = − ω n k \omega_n^{k+\frac{n}{2}}=-\omega_{n}^k ωnk+2n=ωnk

我们将单位根 ω n \omega_n ωn代替x带入多项式,也就是带入 ω n 0 , ω n 1 , ⋯ &ThinSpace; , ω n n − 1 \omega_n^0,\omega_n^1,\cdots,\omega_n^{n-1} ωn0,ωn1,,ωnn1,得到一种特殊的点值表示,这就是傅里叶变换
y k = ∑ i = 0 n − 1 c i ( ω n k ) i y_k=\sum_{i=0}^{n-1}c_i(\omega_n^{k})^i yk=i=0n1ci(ωnk)i
( y 0 y 1 y 2 ⋮ y n − 1 ) = ( 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 ) 2 ) ( c 0 c 1 c 2 ⋮ c n − 1 ) \begin{pmatrix}y_0\\y_1\\y_2\\\vdots\\y_{n-1}\end{pmatrix}= \begin{pmatrix}1 &amp; 1 &amp; 1 &amp; \cdots &amp; 1\\ 1 &amp; \omega_n^{1} &amp; \omega_n^{2} &amp; \cdots &amp; \omega_n^{n-1}\\ 1&amp;\omega_n^{2}&amp;\omega_n^{4}&amp;\cdots&amp;\omega_n^{2(n-1)}\\ \vdots&amp;\vdots&amp;\vdots&amp;\ddots&amp;\vdots\\ 1&amp;\omega_n^{n-1}&amp;\omega_n^{2(n-1)}&amp;\cdots&amp;\omega_n^{(n-1)^2} \end{pmatrix} \begin{pmatrix} c_0\\ c_1\\ c_2\\ \vdots\\ c_{n-1} \end{pmatrix} y0y1y2yn1=11111ωn1ωn2ωnn11ωn2ωn4ωn2(n1)1ωnn1ωn2(n1)ωn(n1)2c0c1c2cn1
而逆变换 c k = 1 n ∑ i = 0 n − 1 y i ( ω n − k ) i = 1 n ∑ i = 0 n − 1 ( ∑ j = 0 n − 1 c j ( ω n i ) j ) ( ω n − k ) i = 1 n ∑ j = 0 n − 1 c j ( ∑ i = 0 n − 1 ( ω n j − k ) i ) = c k \begin{aligned}c_k&amp;=\cfrac{1}{n}\sum_{i=0}^{n-1}y_i(\omega_n^{-k})^i\\ &amp;=\cfrac{1}{n}\sum_{i=0}^{n-1}(\sum_{j=0}^{n-1}c_j(\omega_n^i)^j)(\omega_n^{-k})^i\\ &amp;=\cfrac{1}{n}\sum_{j=0}^{n-1}c_j(\sum_{i=0}^{n-1}(\omega_n^{j-k})^i)\\ &amp;=c_k\end{aligned} ck=n1i=0n1yi(ωnk)i=n1i=0n1(j=0n1cj(ωni)j)(ωnk)i=n1j=0n1cj(i=0n1(ωnjk)i)=ck
故傅里叶逆变换只需要改变傅里叶变换的参数 1 1 1变为 − 1 -1 1并除以 1 n \cfrac{1}{n} n1即可
FFT

说了这么多前置,终于开始了FFT
FFT采用分治的思想,将奇数项和偶数项分开。
对于一个多项式
A ( x ) = ( a 0 + a 2 x 2 + ⋯ + a n − 2 x n − 2 ) + ( a 1 x 1 + a 3 x 3 + ⋯ + a n − 1 x n − 1 ) A(x)=(a_0+a_2x^2+\cdots+a_{n-2}x^{n-2})+(a_1x^1+a^3x^3+\cdots+a_{n-1}x^{n-1}) A(x)=(a0+a2x2++an2xn2)+(a1x1+a3x3++an1xn1)

A ( x ) = ( 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})+x(a_1+a^3x^2+\cdots+a_{n-1}x^{n-2}) A(x)=(a0+a2x2++an2xn2)+x(a1+a3x2++an1xn2)
我们设 A 1 ( x ) = a 0 + a 2 x 2 + ⋯ + a n − 2 x n − 2 , A 2 ( x ) = ( a 1 + a 3 x 2 + ⋯ + a n − 1 x n − 2 ) A_1(x)=a_0+a_2x^2+\cdots+a_{n-2}x^{n-2},A_2(x)=(a_1+a^3x^2+\cdots+a_{n-1}x^{n-2}) A1(x)=a0+a2x2++an2xn2,A2(x)=(a1+a3x2++an1xn2)
所以 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)
x = ω n k x=\omega_n^k x=ωnk代入:
A ( ω n k ) = A 1 ( ω n 2 k ) + ω n k A 2 ( ω n 2 k ) A(\omega_n^k)=A_1(\omega_n^{2k})+\omega_n^{k}A_2(\omega_n^{2k}) A(ωnk)=A1(ωn2k)+ωnkA2(ωn2k)
这样分治必须保证 n = 2 m n=2^m n=2m,否则无法保证分治后左右长度相同,会导致程序出错。因此我们在第一次DFT之前就要将该多项式先补成元素个数为 2 m 2^m 2m并且最高项次数为 n − 1 n-1 n1的形式,补上去的项的系数为0
补成 2 m 2^m 2m项后,我们可以不断分治,最后每个小部分都剩下一个项,会发现一个规律。
初始: { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 } \{0,1,2,3,4,5,6,7\} {0,1,2,3,4,5,6,7}
第一次分治: { 0 , 2 , 4 , 6 } \{0,2,4,6\} {0,2,4,6} { 1 , 3 , 5 , 7 } \{1,3,5,7\} {1,3,5,7}
第二次分治: { 0 , 4 } { 2 , 6 } { 1 , 5 } { 3 , 7 } \{0,4\}\{2,6\}\{1,5\}\{3,7\} {0,4}{2,6}{1,5}{3,7}
第三次分治: { 0 } { 4 } { 2 } { 6 } { 1 } { 5 } { 3 } { 7 } \{0\}\{4\}\{2\}\{6\}\{1\}\{5\}\{3\}\{7\} {0}{4}{2}{6}{1}{5}{3}{7}
蝶形算法
规律:
0->000->000->0,所以0在第0个位置
1->001->100->4,所以1在第4个位置
2->010->010->2,所以2在第2个位置
3->011->110->6,所以3在第6个位置
4->100->001->1,所以4在第1个位置
5->101->101->5,所以5在第5个位置
6->110->011->3,所以6在第3个位置
7->111->111->7,所以7在第7个位置
即每个数的二进制数反转后的值即为它所在位置
用该规律补全多项式后,进行傅里叶变换即可。

附kuangbin大神模板一份:

 
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <queue>
#include <cmath>
#include <string>
#include <cstring>
#include <map>
#include <set>
#include <cmath>
#include <tr1/unordered_map>
 
using namespace std;
#define me(x,y) memset(x,y,sizeof x)
#define MIN(x,y) x < y ? x : y
#define MAX(x,y) x > y ? x : y
#define re register
typedef long long ll;
typedef unsigned long long ull;
const double eps = 1e-08;
const double PI = acos(-1.0);
//复数结构体
struct Complex{
	double x,y;//实部和虚部 x+yi
	Complex(double _x = 0.0,double _y = 0.0){
		x = _x;
		y = _y;
	}
	Complex operator - (const Complex &b)const{
		return Complex(x - b.x,y - b.y);
	}	
	Complex operator +(const Complex &b)const{
		return Complex(x+b.x,y+b.y);
	}
	Complex operator *(const Complex &b)const{
		return Complex(x*b.x - y*b.y,x*b.y+y*b.x);
	}
};
/*
* 进行 FFT 和 IFFT 前的反转变换。
* 位置 i 和(i 二进制反转后位置)互换
* len 必须为 2 的幂
*/
void change(Complex y[],int len){
	int i,j,k;
	for(i = 1, j = len/2;i <len - 1;i++){
		if(i < j)swap(y[i],y[j]);
		//交换互为小标反转的元素,i<j 保证交换一次
		//i 做正常的 +1,j 左反转类型的 +1, 始终保持 i 和 j 是反转的
		k = len/2;
		while(j >= k){
			j -= k;
			k /= 2;
		}
		if(j < k)j += k;
	}
}
/*
* 做 FFT
* len 必须为2^k形式
* on==1 时是 DFT,on==-1 时是 IDFT
*/
void fft(Complex y[],int len,int on){
	change(y,len);
	for(int h = 2; h <= len; h <<= 1){
		Complex wn(cos(-on*2*PI/h),sin(-on*2*PI/h));
		for(int j = 0;j < len;j+=h){
			Complex w(1,0);
			for(int k = j;k < j+h/2;k++){
				Complex u = y[k];
				Complex t = w*y[k+h/2];
				y[k] = u+t;
				y[k+h/2] = u - t;
				w = w*wn;
			}
		}
	}
	if(on == -1)
		for(int i = 0;i < len;i++)
			y[i].x /= len;
}
const int MAXN = 200010;
Complex x1[MAXN],x2[MAXN];
char str1[MAXN/2],str2[MAXN/2];
int sum[MAXN];
int main(){
	while(scanf("%s%s",str1,str2)==2){
		int len1 = strlen(str1);
		int len2 = strlen(str2);
		int len = 1;
		while(len < len1*2 || len < len2*2)len<<=1;
		for(int i = 0;i < len1;i++)
			x1[i] = Complex(str1[len1 - 1 - i] - '0',0);
		for(int i = len1;i < len;i++)
			x1[i] = Complex(0,0);
		for(int i = 0;i < len2;i++)
			x2[i] = Complex(str2[len2 - 1 - i] - '0',0);
		for(int i = len2;i < len;i++)
			x2[i] = Complex(0,0);
		//求 DFT
		fft(x1,len,1);
		fft(x2,len,1);
		for(int i = 0;i < len;i++)
			x1[i] = x1[i]*x2[i];
		fft(x1,len, - 1);
		for(int i = 0;i < len;i++)
			sum[i] = (int)(x1[i].x+0.5);
		for(int i = 0;i < len;i++){
			sum[i+1]+=sum[i]/10;
			sum[i]%=10;
		}
		len = len1+len2 - 1;
		while(sum[len] <= 0 && len > 0)len-- ;
		for(int i = len;i >= 0;i-- )
			printf("%c",sum[i]+'0');
		printf("\n");	
	}
	return 0;
}

 
/*
 
 
*/


学fft时看过参考过的博客:
https://blog.csdn.net/qq_37136305/article/details/81184873
https://blog.csdn.net/enjoy_pascal/article/details/81478582
https://blog.csdn.net/under_sky_dxj/article/details/52778350

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值