FFT可爱QwQ!!!

upd 2023.11.23:

发现自己 FFT 老忘,所以决定在文章开头弄个速通版 FFT 讲解。

给定多项式 A,B 的系数表示,求 C=A×B 的系数表示,FFT 大致操作:

  • 分别求 A,B 的点值表示,求出来之后将对应位相乘得到 C 的点值表示:

    • 拆分多项式:C(x)=C1(x2)+xC(x2),C1(x)=x0+x1x+,C2(x)=x1+x3x+,对于 C1/2(x) 的系数拆分,可运用二进制位翻转来直接进行位置互换。

      贴个二进制位翻转的代码。

      int sum_len = len_a+len_b, len = 0; while (n <= sum_len) ++len, n <<= 1; for (int i = 1; i < n; i++) r[i] = (r[i >> 1] >> 1) | ((i & 1) << (len - 1)); for (int i = 0; i < n; i++) if (i < r[i]) swap(a[i], a[r[i]]);
    • 然后再运用式子进行C(x) 的离散傅里叶变换求值:

      • i[0,n2):C(ωni)=C1(ωn2i)+ωniC2(ωn2i)
      • i[n2,n1]:C(ωni)=C1(ωn2in2)ωnin2C2(ωn2in2)
    • 根据 A,B 的对应位相乘得到 C 的点值表示,时间复杂度 O(nlog2n)

  • C 的点值表示作为多项式 D 的系数,求多项式 D 的离散傅里叶变换 (e0,e1,,en1),最后用关系式 ci=ein 进行系数计算即可。

  • 关于复数运算的常数优化:单位根在每次准备开始迭代时就提前算好,不要每次需要调用时才算,不然会增大常数。

    • 这部分顺便贴个代码吧。
    for (int len = 2; len <= n; len <<= 1) { cp wn(cos(P * 2 / len), inv * sin(P * 2 / len)); for (int st = 0; st + len <= n; st += len) { cp ax(1, 0); for (int j = st; j < st + (len >> 1); j++, ax = ax * wn) { cp a1 = a[j], a2 = a[j + (len >> 1)] * ax; a[j] = a1 + a2, a[j + (len >> 1)] = a1 - a2; } } }
  • 三次变两次:直接将 B 的系数放虚部,跑离散傅里叶变换,平方,跑离散傅里叶变换,除 2n,即可得到系数。


Reference meterial


O(n log2n) 的多项式乘法:FFT

Part 0. 点值表示&系数表示

0.1 点值表示

  • 对于次数为 n 的多项式,我们很容易发现,只要我们代入 n+1 个不同的 x 值求得 n+1 个函数值,这 n+1 个函数值完全可以代替原来多项式的系数表示,让我们能够通过拉格朗日插值法 O(n) 求出任意一个函数值。

  • 也就是说想要通过函数值表示一个次数为 n 的多项式我们只需要 n+1x 的取值不同的函数值就可以表示出这个多项式。

  • 此时我们用 n+1 个点值表示一个次数为 n 的多项式,故称其为点值表示。

PS:拉插不是 FFT 的重点,所以这里为了日后vicky复习 FFT 时不犯迷糊着想,就不对拉插进行过多阐述。想学的话自己搜blog吧,这玩意还是蛮好学的。

0.2 系数表示

  • 我写由题意得应该不会被骂吧。
  • 具体例子就是: A(x)=a0+a1x+x2x2++anxn,这就是系数表示。

Part 1.FFT 思路概述

现在我们有三个多项式:

A(x)=a0+a1x+a2x2++an1xn1,B(x)=b0++bn1xn1

C(x)=A(x)B(x)=i=02n2cixi

对于多项式 C(x)=A(x)B(x) 而言,我们如果用点值表示这个多项式,因为它的次数为 2n2 ,所以我们只需要求出 2n1C(x) 的函数值,就可以在 O(n) 的时间复杂度内求出多项式 A(x)B(x) 的乘积。

那么此时求两个多项式相乘的思路其实就很明确了:

  1. 将多项式 A(x)B(x) 由系数表示转化为点值表示,然后然后将它们俩相乘。【每求解一个 C(x) 所需要的时间均为 O(n)C(x) 若要使用点值表示则需要求 2n+1C(x) 的点值,所以这里如果使用朴素算法转化点值表示的话时间复杂度就是 O(n2)
  2. C(x) 的点值表示转化为系数表示,然后我们就可以非常自然地求得任意 C(x) 了。(这里用拉插的话时间复杂度又是 O(n2)
  3. 那么, FFT 要做的其实就是用一些神奇技巧来将两个转化过程简化。

Part 2. 关于复数

这里坚持住的话后面的 FFT 理解起来其实就很简单了。

首先我们了解一些概念。

对于一个复数 a+bi,我们有两种理解方式:(因为这些都是vicky边口胡边自学的,所以有错记得跟我说一声

  • 一个起点为原点的向量。
  • 平面直角坐标系上的点 (a,b)

两个复数相乘的规则如下:模长相乘,幅角相加。

用代数式进行表示的话就是:

(a+bi)(c+di)=ac+bdi2+adi+cbi=(acbd)+(ad+cb)i

模长就是点 (a,b) 到原点的距离,这里也可以理解为向量的模长。幅角为该复数所对应的向量与 x 轴的逆时针夹角。

如下图:

图中 P6 的幅角为 270 度,(P3)2=P6

幅角相加的概念要理解到位。

介绍了基本虚数的概念之后,我们此时再引入一个新的概念:ωni

我们以原点为圆心作一个圆,取该圆与 x 轴正半轴的交点所表示的那个复数作为第一个点,我们将其命名为 ωn0

我们将这个圆平均分为 n 份,每一份都代表着一个在圆上的复数,即 ωni ,我们将复数 ωn0 看作一个向量,对它进行若干次旋转,每次旋转都转 360n度,那么当我们进行了 i 次旋转之后我们就会得到新的复数 ωni%n ,其幅角为 360ni 度。

如上图,P0ω80P5ω85 ,即 ω80 经过 5 次旋转之后得到的幅角为 36085=225 度的复数。

总结一下,ωni 的意义即为:

  • 将一个以原点为圆心的圆等分n 份,每一份对应圆上的一个代表复数的点。
  • 定义该圆与x轴正半轴的交点为 ωn0
  • 该复数的幅角度数为 360ni 度,也可以理解为它在 ωn0 幅角的基础上加了 i 份度数为 360n 度的幅角(即 ωn0 等角度地旋转 i 次得到 ωni )。

那么,这个 ωnk 该怎么计算呢?

套公式即可:

ωnk=cos(2πkn)+isin(2πkn)

具体为啥是这个公式,只要稍微了解一下三角函数的弧度制即可理解该公式。

且根据复数相乘幅角相加的规则以及 ωni 的定义,我们很容易得到不同复数间的一些关系:

  • ωn2i=(ωni)2=ωn2i

  • ωni=ωni%n

  • ωni+n2=ωni

其中最后一条关系还牵及到一个概念:共轭复数

我们称ωni+n2ωni 互为共轭复数。正式一点地描述,即实部相等,虚部互为相反数的两复数互为共轭复数。从几何意义上来说,两个共轭复数所对应的向量是关于 x 轴对称的。

上面的两个式子直接套 ωni 上下标的定义就可以很容易证得了吧。QwQ

离散傅里叶变换

我知道你很慌但你先别慌。

离散傅里叶变换其实只是一个概念而已,没啥难理解的地方。

上面说过了,对于一个次数为 n 的多项式 f(x) ,若我们要对其用点值表示,则需要取 n+1不同的 x 值代入得到 n+1 个函数值来表示这个函数 f(x)

那么这 n+1 个不同的 x 值我们为何不去取 (ωn+10,,ωn+1n) 上的值呢?

因为正经人谁会想到这种奇怪的取法啊喂! QAQ

离散傅里叶变换的定义其实就是对于一个次数为 n 的函数 f(x) ,我们取函数 f(x)(ωn+10,,ωn+1n) 上的取值作为点值表示。

简而论之,一个函数在 n+1 个特殊位置上取值并将其作为该函数的点值表示,这个点值表示就是离散傅里叶变换

再说一遍!一个函数的离散傅里叶变换是该函数的点值表示而不是单独一个点值!(写给我自己看的,没有任何侮辱各位智商的意思在。

Part 3. 复数在 FFT 的两个变换过程中的运用

搞定了复数!我们其实离搞懂 FFT 就差推几个式子的功夫啦!QwQ

下面的式子推导对我这样的数学弱智来说都还是很友好的,所以说其他人完全不用怕搞不懂 FFT 的式子推导就是了。

实在推不出来你把结论记下来感觉其实也没啥问题。

Part 3.0 回顾

咱们先回顾一下 FFT 的两个转化过程:

  • 系数表示 -> 点值表示。(具体来说是利用多项式 A(x)B(x) 的系数表示求得 C(x) 的点值表示。)
  • 点值表示 -> 系数表示。(将 C(x) 的点值表示转化为系数表示。)

Part 3.1 系数表示 -> 点值表示

我们现有多项式 C(x)=A(x)B(x)=c0++c2n2x2n2

现在我们再弄两个多项式出来:

C1(x)=c0+c2x++c2n2xn1

C2(x)=c1+c3x++c2n1xn1

此时我们就可以将 C(x) 表示如下:

C(x)=C1(x2)+xC2(x2)

那么若我们将 C(x) 的系数表示转化为 C(x) 对应的离散傅里叶变换。

系数表示 -> 点值表示

为了下面的式子推导看上去更简洁,我们将 C(x) 的次数设为 n1 ,即函数 C(x) 的离散傅里叶变换在 (ωn0,,ωnn1) 处取值。(不然满屏的 ωn+1i谁受得了啊喂!QAQ

那么对于任意 C(x)ωni 处的点值求值,我们可以将点值求值过程分类讨论后进行转化:

温馨提示: ωn0=ωnn=1,ωnn2=1

  1. 0i<n2

C(ωni)=C1((ωni)2)+ωniC2((ωni)2)

=C1(ωn2i)+ωniC2(ωn2i)

=C1(ωn2i)+ωniC2(ωn2i)

  1. n2in,设 i=k+n2(0k<n2)

C(ωni)=C(ωnk+n2)

=C1(ωn2k+n)+ωnk+n2C2(ωn2k+n)

=C1(ωn2k+n2)+ωnk+n2C2(ωn2k+n2)

=C1(ωn2kωnn)+ωnk+n2C2(ωn2kωnn)

=C1(ωn2k)+(ωnkωnn2)C2(ωn2k)

=C1(ωn2k)+(ωnk1)C2(ωn2k)

=C1(ωn2k)ωnkC2(ωn2k)

我们对比一下两种情况下 C(ωni) 最终化出来的式子:

  • C(ωni)=C1(ωn2i)+ωniC2(ωn2i)
  • C(ωni)=C1(ωn2k)ωnkC2(ωn2k)

发现没有,我们通过式子推导,使得我们只需要求函数 C1,C2(ωn20,,ωn2n21)处的取值就可以求得函数 C 的点值了。

现在问题就变得很简单了,我们只需递归实现即可,碰到递归边界 n=1 时我们直接套 C(ωni)=A(ωni)B(ωni) 求值返回即可。

现在我们看回时间复杂度。

对于每一个 C(ωni)(0i<n) ,如果用递归实现,递归边界为显然为 C(ωni) 中的 n 的值为 1

每次 C(ωni) 中的 n 都会缩小一半,不难得到我们要进行 log2n 次递归,即每次求 C(ωni) 的点值的时间复杂度为 O(log2n),我们一共要求的点值总数与 n 成正比,所以求 C(x) 的点值表示所需的时间复杂度为 O(n log n)

但是,我们不能忽视的是,O(nlog2n) 仅仅只是递归实现 FFT 的渐进时间复杂度而已,也就是说,递归实现的 FFT 自带的常数在某些凉心畜题人的数据下还是很容易被卡成 TLE 的。

事实上我们有更快的非递归 FFT 实现方式,且vicky个人认为非递归版本并不比递归版本难写多少。

都学到这里了不把 FFT 的优化学完不就前功尽弃了嘛!QAQ

但是为了日后vicky看这篇帖子不会被冲昏头脑,所以我们先把 FFT 的基本思想讲完再进一步对 FFT 的代码实现进行讲解。QwQ

Part 3.2 点值表示 -> 系数表示

这部分主要也是推式子,而且相对上一部分来说这部分的式子推导还是友好很多的。

但是上一部分的其实也不难,直接套 ω 的定义就能推出来了对吧。

说实话其实这部分直接记结论也完全没有问题的。

在上一部分中我们求的是 C(x)(ωn0,,ωnn1) 处取值的点值表示,也就是它的离散傅里叶变换,那么我们是否可以这些在点值上进行一些瞎搞去求得 C(x) 的系数表示呢?

答案是可以的。

为了下面的式子推导可读性更强,我们先将 C(x) 的离散傅里叶变换 (C(ωn0),,C(ωnn1)) 分别设为数组 (d0,d1,,dn1)

我们将 C(x) 的离散傅里叶变换 (d0,,dn1) 作为另一个次数为 n1 的多项式 D(x) 的系数表示,即:

D(x)=C(ωn0)+C(ωn1)x++C(ωnn1)xn1

=d0+d1x++dn1xn1

根据 di 的定义,显然有 di=C(ωni)=j=0n1cj(ωni)j,其中 cj 为多项式 Cj 次项系数。

我们再将 D(x) 的离散傅里叶变换表示成 (e0,,en1),但是这里需要注意的是,我们不再是在 (ωn0,,ωnn1) 处取值,而是在 (ωn0,ωn1,,ωn1n) 处进行取值,即 ek=D(ωnk)=i=0n1di(ωnk)i

那么对于每一个 ei ,我们可以尝试一下对它进行式子推导,看看能否找出一种通过 ei 快速求出 ci 的办法:

ek=D(ωnk)=i=0n1di(ωnk)i=i=0n1D(ωni)(ωnk)i

=i=0n1(j=0n1cj(ωni)j)(ωnk)i

=i=0n1j=0n1cj(ωni)j(ωnk)i=i=0n1j=0n1cjωnijωnki

=i=0n1j=0n1cjωni(jk)=i=0n1j=0n1cj(ωnjk)i

=j=0n1cji=0n1(ωnjk)i

k 显然可以理解成一个定值,我们枚举到每一个 j 的时候,j 其实也算作是一个定值,此时不难发现 i=0n1(ωnjk)i 其实就是个等比数列求和。

因为 j 的值是在 [0,n1] 范围内进行枚举的,因此我们可以对 jk 进行分类讨论,然后再求 i=0n1(ωnjk)i 的值:

  1. jk=0,即 j=k

此时显然有:

i=0n1(ωnjk)i=i=0n1(ωn0)i=i=0n11i=n

  1. jk0

根据等比数列求和公式 sum=a1(1qn)1q ,我们易得:

PS:公式中的 a1 为数列首项,q 为公比,n 为项数。

为了下面的式子推导看起来更简洁一些,我们设 W=jk

注意,下面式子中的 jk 我们均看作定值。

i=0n1(ωnjk)i=i=0n1(ωnW)i

=1(1(ωnW)n)1ωnW=1(ωnn)W1ωnW

=11W1ωnW=111ωnW=0

也就是说,当且仅当 j=0n1 枚举到 k 时,i=0n1(ωnjk)i 才会有值,且此时i=0n1(ωnjk)i 的值为 n

那么我们就可以进一步转化 ek

ek=j=0n1cji=0n1(ωnjk)i

我们按上面分类讨论的两种情况进行求值:

ek=(j=0,jkn1cji=0n1(ωnjk)i)+cki=0n1(ωn0)i

=(j=0,jkn1cj0)+ckn

=ckn

这个时候要怎么求 ck 就不用我说了吧。 QwQ

ck=ekn

此时我们运用上一部分中的系数表示->点值表示的方法即可在 O(log2n) 的时间复杂度下求每个 ek 也就是 D(ωnk) 的值,每求出一个 ek ,我们就可以运用 ck=ekn 这一关系 O(1) 求出 C(x) 的第 k 项系数。

C(x) 的项数与 n 成正比,所以我们运用这一方法实现点值表示->系数表示的渐进时间复杂度即为 O(nlog2n)

Part 3.3 回顾 FFT 的全过程

FFT 是一种可以在 O(n log2n) 的渐进时间复杂度内求次数都为 n 的两个多项式 A(x)B(x) 的卷积 C(x) 的值的算法。

  • Part 3.1

    • C(x)=A(x)B(x),分治求A(x),B(x) 的离散傅里叶变换,然后求对应的 C(x) 的离散傅里叶变换,渐进时间复杂度为 O(nlog2n)
  • Part 3.2

    • C(x) 的离散傅里叶变换作为另一个与 C(x) 同次的多项式 D(x) 的系数,每次用 Part 3.1 中的办法 O(log2n) 求每个 D(x)(ω2n10,ω2n11,,ω2n1(2n2)) 处取值的得到点值 ei,根据 ci=ein 的关系求 C(x) 的每一项系数,求 C(x)2n2 项系数的渐进时间复杂度为 O(n log2n)
    • 求出 C(x) 的每一项系数之后,因为 C(x)=A(x)B(x) ,所以对于多项式 A(x)B(x) 的乘积,我们直接将 x 代入系数表示下 C(x)O(n) 渐进时间复杂度下进行求解即可。

所以 FFT 的渐进时间复杂度为 O(nlog2n)

放一下板题(P3803 【模板】多项式乘法(FFT))递归写法的代码:

#include<bits/stdc++.h> #define N 4000005 #define P acos(-1.0) using namespace std; int aa,bb; struct cp{ double x,y; cp (double xx=0,double yy=0){ x=xx,y=yy; } }a[N],b[N]; cp operator +(cp a,cp b){ return cp(a.x+b.x,a.y+b.y);} cp operator -(cp a,cp b){ return cp(a.x-b.x,a.y-b.y);} cp operator *(cp a,cp b){ return cp(a.x*b.x-a.y*b.y,a.y*b.x+a.x*b.y);} void fft(cp*,int,int); int main(){ scanf("%d%d",&aa,&bb); for(int i=0;i<=aa;i++) scanf("%lf",&a[i].x); for(int i=0;i<=bb;i++) scanf("%lf",&b[i].x); int n=1; while(n<=aa+bb) n<<=1; fft(a,n,1),fft(b,n,1); for(int i=0;i<n;i++) a[i]=a[i]*b[i]; fft(a,n,-1); for(int i=0;i<=aa+bb;i++) printf("%d ",(int)(a[i].x/n+0.5)); return 0; } void fft(cp *a,int n,int inv){ if(n==1) return ; cp a1[(n>>1)+5],a2[(n>>1)+5]; for(int i=0;i<n;i+=2)//这里是i+=2!!! a1[i>>1]=a[i],a2[i>>1]=a[i+1]; fft(a1,n>>1,inv),fft(a2,n>>1,inv); for(int i=0;i<(n>>1);i++){ cp x(cos(2.0*P*i/n),inv*sin(2.0*P*i/n)),ax=x*a2[i]; a[i]=a1[i]+ax,a[i+(n>>1)]=a1[i]-ax; } }

Part 3.4 FFT 优化

优化一:递归->迭代

迭代即用循环模拟递归的过程,对 FFT 的常数有不小的优化作用。

我们观察一下在做FFT的时候多项式 C(x) 的每一项系数的位置变化。

0 1 2 3 4 5 6 7 0 2 4 6|1 3 5 7 0 4|2 6|1 5|3 7 0|4|2|6|1|5|3|7

这里有一个很奇妙的性质:对于每一个系数,它的最终下标为它原来下标的二进制翻转。

如原来处在位置 6(=(110)2) 的系数最终到了位置 3(=(011)2)

那么此时我们其实就可以把要转换的多项式 C(x) 的系数一开始就调换到它的最终位置,然后再通过循环模拟递归的过程。

代码也很简单:

int sum_len = len_a+len_b, len = 0; while (n <= sum_len) ++len, n <<= 1; for (int i = 1; i < n; i++) r[i] = (r[i >> 1] >> 1) | ((i & 1) << (len - 1)); for (int i = 0; i < n; i++) if (i < r[i]) swap(a[i], a[r[i]]); for (int len = 2; len <= n; len <<= 1) { cp wn(cos(P * 2 / len), inv * sin(P * 2 / len)); for (int st = 0; st + len <= n; st += len) { cp ax(1, 0); for (int j = st; j < st + (len >> 1); j++, ax = ax * wn) { cp a1 = a[j], a2 = a[j + (len >> 1)] * ax; a[j] = a1 + a2, a[j + (len >> 1)] = a1 - a2; } } }

优化二:减少复数乘法运算次数

而且实现FFT时还有一点十分重要,就是转化过程中的复数乘法运算。

有图有真相:D

让我们来一起看看两份代码有什么区别吧:D

5.42s

for(int len=2;len<=n;len<<=1){ for(int st=0;st+len-1<n;st+=len){ for(int j=st;j<=st+(len>>1)-1;j++){ cp x(cos(2.0*P*(j-st)/len),inv*sin(2.0*P*(j-st)/len)),ax=x*a[j+(len>>1)],a1=a[j]; a[j]=a1+ax,a[j+(len>>1)]=a1-ax; } } }

2.83s

for(int len=2;len<=n;len<<=1){ complex wn(cos(2*P/len),inv*sin(2*P/len)); for(int st=0;st+len<=n;st+=len){ complex a1,a2,ax(1,0); for(int j=st;j<st+(len>>1);j++,ax=ax*wn) a1=a[j],a2=a[j+(len>>1)]*ax, a[j]=a1+a2,a[j+(len>>1)]=a1-a2; } }

两份代码的其余部分均相同。

两份代码仅仅差了几次的复数运算,最终的运行结果却相差甚远。

所以说,复数乘法这种东西,咱们还是能少算几次就少算几次

在第二份代码中我们就少算了很多次 (cos(2*P/len),inv*sin(2*P/len)) 的值,故常数优化了不少。

因为这里是我感性理解的,并没有找理性证明的博客进行学习,所以如果我说错了的话麻烦在评论区指出一下我的错误 www。awa

优化三:三次FFT变为两次FFT

在我们求 A(x)B(x) 的卷积 C(x) 时,我们会对它们分别做 FFT 转化为点值表示,然后对应每项乘起来得到 C(x) 的点值表示,最后再做一次 FFTC(x) 由点值表示转化为系数表示。

这样的话实际上我们一共进行了三次 FFT ,于是我们考虑能否减少做 FFT 的次数。

当然可以啊,不然我怎么可能写这个板块啊。

我们弄两个系数为复数的多项式,按正常 FFT 的做法,我们应该分别把多项式 A(x)B(x) 的系数放到两个多项式系数的实部上面去。

但是在三次变两次 FFT 优化中,我们只需要弄一个系数为复数的多项式,并将多项式 A(x)B(x) 的系数分别放到该多项式系数的实部和虚部中,然后对这个多项式跑一边 FFT ,然后对它做平方,再做一遍 FFT 逆变换回来,最后取它每一位系数虚部上的数字再 /2n 即可(该多项式次数为 n)。

证明也很简单,了解完全平方公式以及复数概念即可证明。

(a+bi)2=a2+(b2i2)+2abi=a2b2+2abi

此时我们的常数就可以优化到约为原来的 23 了。

这是三个优化都加上了的代码跑出来的:D

最后放个加了优化的代码QwQ

#include<bits/stdc++.h> #define P acos(-1.0) #define N 1000005 using namespace std; int a1,b1,n=1,r[N<<2],ans[N<<2],aa1; struct cp{ double x,y; cp(double xx=0,double yy=0){ x=xx,y=yy;} }a[N<<2],b[N<<2];//这里的复数我用的是结构体表示,实际上复数运算也可以用STL cp operator +(cp a,cp b){ return cp(a.x+b.x,a.y+b.y);} cp operator -(cp a,cp b){ return cp(a.x-b.x,a.y-b.y);} cp operator *(cp a,cp b){ return cp(a.x*b.x-b.y*a.y,a.y*b.x+b.y*a.x);} void pre(),fft(cp*,int); int main(){ scanf("%d%d",&a1,&b1); for(int i=0;i<=a1;i++) scanf("%lf",&a[i].x); for(int i=0;i<=b1;i++) scanf("%lf",&a[i].y); pre(),fft(a,1); for(int i=0;i<n;i++) a[i]=a[i]*a[i]; fft(a,-1); for(int i=0;i<=a1+b1;i++) printf("%d ",(int)(a[i].y/n/2+0.5));//取虚部求最终的系数表示 return 0; } void pre(){ aa1=max(a1,b1)<<1; while(n<=aa1) n<<=1; int len=1; while((1<<len)<=aa1) ++len; for(int i=1;i<n;i++) r[i]=((r[i>>1]>>1|(i&1)<<(len-1)));//预处理每一个系数的最终位置(二进制翻转) } void fft(cp *a,int inv){ for(int i=0;i<n;i++) if(i<r[i]) swap(a[r[i]],a[i]); //用循环模拟递归的过程 for(int len=2;len<=n;len<<=1){ cp wn(cos(P*2/len),inv*sin(P*2/len));//提前算好单位复数根,减少重复运算 for(int st=0;st+len-1<n;st+=len){ cp ax(1,0); for(int j=st;j<st+(len>>1);j++,ax=ax*wn){ cp a1=a[j],a2=ax*a[j+(len>>1)]; a[j]=a1+a2,a[j+(len>>1)]=a1-a2; } } } }

__EOF__

本文作者你的名字
本文链接https://www.cnblogs.com/vicky-like-orange/p/vicky-is-fw.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   vickyの橙子林  阅读(78)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
点击右上角即可分享
微信分享提示