首页 > 其他分享 >FFT 小记

FFT 小记

时间:2023-08-18 19:12:22浏览次数:43  
标签:right 多项式 FFT cdots 复数 小记 omega left

目录

由于懒,所以没图。

写得时候有点抽风,可能有 typo,望指出。

复数

复数表述为 \(a+b\times i\),其中 \(i\) 是复数单位 \(\sqrt{-1}\),同时由此可得 \(i^2=-1\)。

称 \(a\) 是实部(下文简称 real),\(b\) 是虚部(简称 imag)。对于一个实数,其 real 等于其值,imag 为 0。

任意一个复数都可一一映射为二维平面(称作复平面,由实轴和虚轴组成)上一个点 \((real,imag)\)。

幅角是这个点与原点连线后,与实轴正方向形成的夹角;模长是这个点与原点连线的长度,即到原点的距离。

记幅角为 \(\theta\),模长为 \(u\),则复数还可表为等于 \((u\cos(\theta),iu\sin(\theta))\),证明就是在虚平面上把这个点和原点连起来。

复数的加减法相当于 real 和 imag 的分别相加减,乘法则是看作多项式相乘,除法相当于多项式除法(确信)。

复数共轭是把复数的 imag 取反,并且复数的共轭乘上自身可以得到一个实数。

For example:

\[(a+bi)+(c+di)=(a+c)+(b+d)i \\ (a+bi)-(c+di)=(a-c)+(b-d)i \\ (a+bi)\times(c+di)=c(a+bi)+di(a+bi)=ac+bci+adi+bdi^2=(ac-bd)+(bc+ad)i \\ (a+bi)\times(a-bi)=a(a+bi)-bi(a+bi)=a^2+abi-abi+b^2i^2=a^2-b^2 \\ \frac{a+bi}{c+di}=\frac{(a+bi)\times(c-di)}{(c+di)\times(c-di)}=\frac{(ac+bd)+(bd-ad)i}{c^2-d^2}=\frac{ac+bd}{c^2-d^2}+\frac{bd-ad}{c^2-d^2}i \]

最后一个除法用了共轭进行化简,把分母有理化了。

然后这套运算还有几何意义:加减法是点的加减,乘法是模长相乘,幅角相加,共轭是关于实轴对称。

复数还有指数形式的定义,即 \(e^{iu}=\cos(u)+i\sin(u)\)(是不是很眼熟,这个似乎叫欧拉公式),其中 \(e\) 即为自然对数。

具体为什么……我也不清楚。

单位复数根

\(n\) 次单位根(\(\omega_n\) )是满足 \(\omega_n^n=1\) 的复数。

根据我们滴数学啊,\(n\) 次单位根一共有 \(n\) 个,并且为 \(\omega_n\) 的 \(0 \sim n-1\) 次幂(或者说 \(1 \sim n\) 次幂),称 \(\omega_n\) 为主 \(n\) 次单位根。

一个性质是:在复平面上以原点为圆心,画一个半径为 \(1\) 的圆(此时最好工整地在草稿本上画一个圆),这个圆上的点模长均为 \(1\);然后从与实轴正方向的交点开始,把圆周等分为 \(n\) 份,恰好可以得到 \(n\) 个单位根,\(\omega_n^0\) 恰为这个圆和实轴正方向的交点。

证明则考虑当模长小于 \(1\) 的复数幂次越高模长就越小,故不可能为单位根,大于 \(1\) 同理。

啊你说为什么是等分的?你发现模长相等,所以考虑幅角,若把圆周等分为 \(n\) 分,任意一份重复 \(n\) 次恰好就是一整个圆,而整个圆的模长又为 \(1\),所以显然符合(注意,我们这里任意一份对应的复数模长本来就为 \(1\),所以做乘法的时候仍然不考虑)。

而且我们可以得到 \(\omega_n^k=e^{2\pi ik/n}\),这个就是找交点然后揉进式子里。

那么这个 \(\omega_n\),有什么性质呢?

  1. \(\omega_{dn}^{dk}=\omega_n^k\)

    这个比较显然,也符合直觉。

    从代数上看,就是 \(e^{2\pi idk/dn}=e^{2\pi ik/n}\)。

    几何上看,你从实轴正方向开始把连续 \(d\) 个部分合成,毫无问题。

    这个会被称作消去引理。

  2. \(\omega_n^{n/2}=\omega_2=1\)

    同样从原来的式子上看,\(e^{2\pi i\frac n 2/n}=e^{2\pi i/2}=\omega_2=-1\)。

    你从几何上看,就是 \(\omega_n^0\) 关于虚轴的对称,两者都在实轴上且互为相反数。

  3. \(\left\{(\omega_n^i)^2\vert i\in \left[0,n-1\right]\right\}=\left\{\omega_{n/2}^i\vert i\in \left[0,n/2-1\right]\right\}\),要求 \(2\parallel n\)。

    这个东西看这有点高级,它其实是想表述:\(\omega_n^{k}=-\omega_n^{k+n/2}\),同时 \((\omega_n^k)^2=\omega_{n/2}^k\)。

    后一个可以由消去引理得到,前一个又是什么理由呢?就是上一条 \(\omega_n^{n/2}=-1\),然后带了系数。

    注意这个家伙非常滴重要,我们的 FFT 要用。

    这个会被称作折半引理。

  4. \(\sum\limits_{k=0}^{n-1}(\omega_n^d)^k=\frac{(\omega_n^d)^n-1}{\omega_n^d-1}\),要求 \(d\nparallel n\)

    等比数列求和……

    但是同样重要,我们的 FFT 也要用。

    注意那个限制,不满足的话分母会挂(\(\omega_n^d=\omega_{n/d}=1\)),此时和恰为 \(n-1\)。

上面的目前就够了。

Poly

对于一个多项式,一般有两种表述:

  1. \(F(x)=\sum\limits_{i=0}^{n-1} f_i x^i\),这称作系数形式,同时这个多项式是 \(n\) 次的。
  2. \(\left\{\left(x_i,y_i\right)\vert i \in \left[0,n-1\right],F(x_i)=y_i \right\}\),这称作点值形式。

同时我们可以定义多项式的运算,就和竖式的运算类似(为了方便,后文用不同的大小写来区分多项式和多项式的各个系数)。

For example:

\[F(x)+G(x)=\sum\limits_{i=0}^{n-1} (f_i+g_i) x^i \\ F(x)-G(x)=\sum\limits_{i=0}^{n-1} (f_i-g_i) x^i \\ F(x)*G(x)=\sum\limits_{i=0}^{n-1}\sum\limits_{j=0}^{m-1} f_i g_j x^{i+j} \]

除法暂时不管,涉及到多项式求逆。

然后发现系数形式的多项式做加减法是 \(\mathcal{O}(n)\) 的,但乘法是 \(\mathcal{O}(n^2)\)。

不过点值形式就会好做多:

\[\left\{(x_0,y_0)\cdots(x_{n-1},y_{n-1}) \right\}\plusmn\left\{(x_0,y_0’)\cdots(x_{n-1},y_{n-1}‘) \right\}=\left\{(x_0,y_0\plusmn y_0')\cdots(x_{n-1},y_{n-1}\plusmn y_{n-1}') \right\} \\ \left\{(x_0,y_0)\cdots(x_{n-1},y_{n-1}) \right\}\times \left\{(x_0,y_0’)\cdots(x_{n-1},y_{n-1}‘) \right\}=\left\{(x_0,y_0\times y_0')\cdots(x_{n-1},y_{n-1}\times y_{n-1}') \right\} \]

不过对于除法不成立(仍需用多项式求逆),可见《算法导论》习题。

但是点值形式一般不常用,而系数形式和点值形式的转化(求值和插值)直接做又同样是 \(\mathcal{O}(n^2)\)(插值要做到 \(\mathcal{O}(n^2)\) 还需套个拉格朗日插值)。

要快速进行多项式乘法,需要快速求值与插值,但求值和插值应当怎么优化呢?

FFT

正片开始。

傅里叶的做法是:带入 单位根 进行求值/插值。

有什么优势?

我们可以对于任意的 \(n\) 次多项式恰好带入 \(n\) 个 \(n\) 次单位根,同时由折半引理, \(n\) 个 \(n\) 次单位根的平方构成的集合恰好等价于 \(n/2\) 个 \(n/2\) 次单位根勾成的集合,且同一对里面两个数互为相反数。

只要我们构造出两个部分,恰好取到这 \(n/2\) 个平方,就可以保证问题规模搞好缩小到一半。

为了方便,我们假定 \(n\) 都是 \(2^k,k\in \mathbf{N}\),不足的部分可以补 \(f_i=0\) 替代。

构造是容易的,我们可以把多项式 \(F(x)=f_0+f_1x+f_2x^2+\cdots+f_{n-1}x^{n-1}\) 拆做如下两部分:

\[F^{\left[0\right]}(x)=f_0+f_2x+f_4x^2+\cdots+f_{n-2}x^{n/2-1} \\ F^{\left[1\right]}(x)=f_1+f_3x+f_5x^2+\cdots+f_{n-1}x^{n/2-1} \\ \]

即按照下标的奇偶性划分成两个 \(n/2\) 次的多项式。

然后简单观察可以发现如下性质:

\[F(x)=F^{\left[0\right]}(x^2)+xF^{\left[1\right]}(x^2) \]

现在我们将右边的两个多项式计算出来,点值形式大概长成这个样子。

\[F^{\left[0\right]}(x):\left\{(\omega_{n/2}^0,y_0)\cdots(\omega_{n/2}^{n/2-1},y_{n/2-1}) \right\}=\left\{(\omega_{n}^0,y_0)\cdots(\omega_{n}^{n-2},y_{n/2-1}) \right\} \\ F^{\left[1\right]}(x):\left\{(\omega_{n/2}^0,y_0')\cdots(\omega_{n/2}^{n/2-1},y_{n/2-1'}) \right\}=\left\{(\omega_{n}^0,y_0')\cdots(\omega_{n}^{n-2},y_{n/2-1'}) \right\} \\ \]

直接对应位置做乘法,就可以得到 \(F(x)\)。

注意到 \(\omega_n^{k+n/2}=\omega_n^k\),所以实现时循环到 \(n/2\),另一部分只需把 \(F^{\left[1\right]}(x)\) 的系数取反即可。

上面的过程对多项式进行了求值,被称为 DFT(插值对应的叫做 IDFT),贴一个递归的代码实现:

	//Complex 可以手写,也可以借用 STL 中的 std::complex<double>
	void DFT(Complex *f,int n){
		if(n==1) return;//边界,此时本身就是点值形式
		for(int i=0;i<n;++i) tmp[i]=f[i];//临时数组记录f
		for(int i=0;i<n;++i)
			f[(i>>1)+((i&1)?(n>>1):0)]=tmp[i];//划分
		Complex *g=f,*h=f+(n>>1);
		DFT(g,n>>1),DFT(h,n>>1);//递归计算
		Complex w(1,0),step(cos(2*PI/n),sin(2*PI/n));//单位根公式
		for(int i=0;i<(n>>1);++i){
			tmp[i]=g[i]+w*h[i];
			tmp[i+(n>>1)]=g[i]-w*h[i];//上面说的
			w*=step;//维护单位根
		}
		for(int i=0;i<n;++i) f[i]=tmp[i];
	}

完事之后用点值形式对应相乘即可(返回的这个复数数组就是点值形式的 \(y\))。

然后还需要把点值形式变回系数形式,这时需要运用 IDFT。

你需要写个 too huge 的矩阵出来,然后说明这玩意儿是 DFT,然后又证明 IDFT 的系数是什么什么,中途要用到求和引理。

所以懒了,结论是:

  • DFT 的表述为 \(y_i=\sum\limits_{k=0}^{n-1}a_k\omega^{ki}\)
  • IDFT 的表述为 \(f_i=\frac{1}{n}\sum\limits_{k=0}^{n-1}y_k\omega_n^{-ki}\)。

你发现这俩出奇地相似,所以可以套上面 DFT 的做法,写出这么一个函数:

	void IDFT(Complex *f,int n){
		if(n==1) return;
		for(int i=0;i<n;++i) tmp[i]=f[i];
		for(int i=0;i<n;++i)
			f[(i>>1)+(i&1?(n>>1):0)]=tmp[i];
		Complex *g=f,*h=f+(n>>1);
		IDFT(f,n>>1),IDFT(h,n>>1);
		Complex w(1,0),step(cos(2*PI/n),-sin(2*PI/n));//只有这一处不一样……
		for(int i=0;i<(n>>1);++i){
			tmp[i]=g[i]+w*h[i];
			tmp[i+(n>>1)]=g[i]-w*h[i];
			w*=step;
		}
		for(int i=0;i<n;++i) f[i]=tmp[i];
	}
	//主函数内:
	for(int i=0;i<n;++i) f[i]/=Complex(n,0);

然后这俩实在是太像了,为了降低代码长度,可以把他们合并了。

于是板子题的 AC 代码如下:

//主要部分
namespace MAIN{
	using QL::IO::lq;
	using QL::Base_Tools::max;
	constexpr int N=2<<20;
	int n,m;
	using Complex=std::complex<double>;
	const double PI=acos(-1);
	Complex f[N],g[N],h[N],tmp[N];
	void FFT(Complex *f,int n,int type){
		if(n==1) return;
		for(int i=0;i<n;++i) tmp[i]=f[i];
		for(int i=0;i<n;++i)
			f[(i>>1)+((i&1)?(n>>1):0)]=tmp[i];
		Complex *g=f,*h=f+(n>>=1);
		FFT(g,n,type),FFT(h,n,type);
		Complex w(1,0),step(cos(PI/n),type*sin(PI/n));
		for(int i=0;i<n;++i){
			tmp[i]=g[i]+w*h[i];
			tmp[i+n]=g[i]-w*h[i];
			w*=step;
		}
		n<<=1;
		for(int i=0;i<n;++i) f[i]=tmp[i];
	}
	signed _main_(){
		lq.read(n,m);
		for(int i=0,x;i<=n;++i) lq.read(x),f[i]=Complex(x,0);
		for(int i=0,x;i<=m;++i) lq.read(x),g[i]=Complex(x,0);
		int x=1<<(32-__builtin_clz(max(n,m))+1);
		FFT(f,x,1),FFT(g,x,1);
		for(int i=0;i<x;++i) h[i]=f[i]*g[i];
		FFT(h,x,-1);
		//注意浮点数有向下舍去的误差,这里要+0.5
		for(int i=0;i<=(n+m);++i) lq.write(int((h[i]/Complex(x,0)).real()+0.5),' ');
		return 0;
	}
}
signed main(){
	return MAIN::_main_();
}

End

上面的代码中提到了,复数的计算有误差。

而实际上,不仅有误差,它还跑得相当之慢……

所以在这里挖个坑:NTT。

To be continue...

参考资料

command-block 的博客,大佬写得很详细。

OI wiki,这个偏一般。

算法导论,非常给力。

标签:right,多项式,FFT,cdots,复数,小记,omega,left
From: https://www.cnblogs.com/LQ636721/p/17641387.html

相关文章

  • 小记
    1.路由跳转及传参import{withRouter,RouteComponentProps}from'react-router';classReplenishmentOrderextendsComponent<TProps&RouteComponentProps,TState>{constructor(props:RouteComponentProps){super(props);......
  • 8.18 模拟赛小记 & 学习
    谔谔谔谔。菜翻天。今天模拟赛题目传送门。A.跳蚤市场(mid)话说我才看到这个英文名字叫mid。然后就是手写lower_bound和upper_bound优化前缀和。B.组合问题(anm)这个错排之前上课讲过于是一眼了。可惜没看longlong祖宗十八代都被炸死了。C.旅行(day)图论题。D.购物(t......
  • m基于FFT傅里叶变换的256QAM基带信号频偏估计和补偿FPGA实现,含testbench和matlab星座
    1.算法仿真效果本系统进行了Vivado2019.2平台的开发,并使用matlab2022a对结果进行星座图的显示:     频偏基带256qam信号和频偏补偿后的256qam基带信号使用matlab显示星座图,结果如下:   2.算法涉及理论知识概要         FFT傅里叶变换是一种高效的......
  • 「Log」2023.8.17 小记
    序幕早上到校先摆,然后开调代码。大分块对拍调调调。学长开始讲平衡树。平衡树平衡树平衡树!学完了,点午饭吃午饭。学主席树。主席树主席树主席树!学完了点晚饭吃完饭。用chatGPT写了点文章,乐坏了。继续卡常。\(\color{black}{P4119\[Ynoi2018]\未来日记}\)详见「「No......
  • 「Log」2023.8.16 小记
    序幕早上昏迷,九点才到校,少听了四道题,问题不大。点咖啡喝。SAM题也抽象。线段树合并,不会。写个AC自动机板子。\(\color{royalblue}{P3808\【模板】AC\自动机(简单版)}\)板子。\(\text{Link}\)\(\color{royalblue}{P3796\【模板】AC\自动机(加强版)}\)板子。\(\text{Li......
  • 恋爱小记
    也不知道为啥要写,就是当给自己看的,并作为纪念,以后纪念日能找素材?8.14rp极高划水8.15rp极低,遵循rp守恒上午她说自己静静,然后一个小时后心态崩溃说分手,我知道她肯定乱想了,然后我拼命拉,把我能想到的方法都试了,最后还是我说,你要分,将来等着吧(把她吓到了)。然后才慢慢跟我说了......
  • 「Log」2023.8.15 小记
    序幕七点多到校,整理博客,开始调昨天没整完的题。手算哈希,把所有部分都先改成暴力。好消息,暴力没问题,准备改成正解。学长开始讲课,AC自动机,秒了。接着调题,过了。开心。\(\color{royalblue}{P9399\「DBOI」Round\1\人生如树}\)考虑用哈希判断两个序列是否相等,偏移量可以预......
  • YsOI2023 小记
    D2T1签。#include<cstdio>usingnamespacestd;intread(){/*...*/}typedeflonglongll;voidsolve(){ lln=read()-1,x=read(); lly=x; while(~y&1)y>>=1; for(inti=1;i<n;++i)y<<=1; if(x>y)y=x; printf("%lld\n"......
  • 基于FFT傅里叶变换的64QAM基带信号频偏估计和补偿算法FPGA实现,包含testbench和matlab
    1.算法仿真效果本系统进行了Vivado2019.2平台的开发,并使用matlab2022a对结果进行星座图的显示:    将FPGA的频偏基带QPSK信号和频偏补偿后的QPSK基带信号使用matlab显示星座图,结果如下:   2.算法涉及理论知识概要        FFT傅里叶变换是一种高效的......
  • 定点补码乘法器小记
    目录硬件模拟软件无脑乘Booth乘法器华莱士树优化的华莱士树参考链接:《计算机体系结构基础第三版》定点补码乘法器一生一芯学习讲义一生一芯视频号硬件模拟软件软件方式即类似我们手工计算,如计算1101*0101+00001101(乘数最低位1,结果加上被乘数。将乘数右移,被乘数左移)+0......