序言
这篇文章是自己看的,外面有这么多优秀的博客多好。
这是我在复赛前那个暑假的一个噩梦。。。最终我还是花了一天时间搞出来了,这说明我天赋一般,虽然即使说明了我天赋一般也没有什么用。。该干啥还是得干啥。其实天赋没啥用的,真正的是方法什么的比较重要,以及平时各种能力的积累,最终体现在天赋上,也就是说天赋是可以改变的,天赋和智商差距应该还是蛮大的。我在说啥。。。。。
感谢很多优秀的博主带来的优秀的文章,感谢同机房的yyn带了了符合我们机房风格的代码板子。。。。
FFT/NTT是干嘛的?
FFT中文叫做快速傅里叶变换。
就是在O(nlogn)的时间里面求出多项式乘法,如果硬乘的话是O(n^2).
当然还有一个作用就是求卷积,在O(nlogn)求出f(1)~f(n),其中f(i)=sum{g(j)*g(k-j),0<=j<=i},不会卷积没有关系的,大概看一下定义就行了,多项式乘法的实质其实也算是个卷积吧。
至于做题的话一个是优化多项式乘法,一个是求卷积形式的数学题,一般来说就是推公式推出卷积形式就套模板吧,
此外,这个真的就是个板子,因此即使有些细节无法理解,只要背下来问题也就不大了。
NTT中文叫做快速数论变换。
NTT基本上和FFT差不多,代码结构基本上完全一样,NTT对于有mod的,会比较快而且没有精度误差,因为它是建立在整数上的情况而不是FFT建立在复数的情况。
FFT原理
注:我们这里按照多项式乘法来理解
优化原理
我们是用什么方法是的O(n^2)的算法变成了O(nlogn)的呢?
首先我们需要知道,多项式有两种表示方法,系数表示法和点值表示法,系数表示法就是直接记录系数,点值表示法就是带入n个不同值的点,得到答案,也就是n项的多项式用n个(x,y)表示(n个方程解n个系数嘛)。
所以我们有一种叫做DFT的变换可以使得系数表达式通过O(nlogn)的时间变成点值表达式,然后点值表达式O(n)就能做完乘法,又可以用几乎相同的方法在O(nlogn)的时间里面变成系数表达式,最终就完成了时间复杂度O(nlogn)的做法。
当然这只是个意思,实际上肯定还是有很多数学技巧的。
实现原理
wn的递推是第一个用三角形式,后面的用指数形式。
A(x)=a0+a1∗x+a2∗x2+a3∗x3+a4∗x4+a5∗x5+⋯+an−2∗xn−2+an−1∗xn−1 $
A1(x)=a0+a2∗x+a4∗x2+⋯+an−2∗xn/2−1
A2(x)=a1∗x+a3∗x+a5∗x2+⋯+an−1∗xn/2−1
A(x)=A1(x^2)+x*A2(x ^2)
大概就是这个意思,加上单位复数根的一些性质,加上蝴蝶变换模仿递归操作,记住代码就好了。
其中x^2经过递归刚刚符合,wn(k,n)=-wn(k+n/2,n),所以一个加一个减
最后逆变换是用了一个矩阵思想,反矩阵,主要答案要/n;
一些操作
分治FFT
FFT可以完全看作乘法运算(比如它用来优化高精度乘法,而高精度乘法本身就是卷积运算),只不过是对于一个数组(多项式)进行操作。
所以有时候遇到快速幂的形式是可以用快速幂来做的。
循环卷积
有时候要把f[n]的值算作f[n%m]的值,那么累加上去即可,比如bzoj4827 [Hnoi2017]礼物
具体来说就是比如正常的是a1 * bn + a2 * bn-1+..
然后你要a1* b1+ a2 * bn +a3 * bn-1+…
序列反转
a1 *b1+ a2 *b2+…
把b序列反转了就变成了卷积形式
代码
递归处理应该比较好理解,然后看非递归,我们通过观察发现在递归处理一些元素之后原来的第i个元素,新下标恰好是i在二进制下的反转,因此我们通过实现预处理初最终位置来进行非递归,这一部也叫做蝴蝶变换,因为叉过来叉过去的很像蝴蝶嘛。
FFT
BZOJ2179 FFT(A*B)
#include<cmath>
#include<queue>
#include<cctype>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=150000+105;
const double pi=acos(-1);
struct Complex
{
double x,y;
Complex(){};
Complex(double _x,double _y){x=_x,y=_y;};//注意这里的几个赋值操作
friend Complex operator+(Complex a,Complex b)
{
return Complex(a.x+b.x,a.y+b.y);
}
friend Complex operator-(Complex a,Complex b)
{
return Complex(a.x-b.x,a.y-b.y);
}
friend Complex operator*(Complex a,Complex b)//(a+bi)*(c+di)=ac+bd*i^2+(ad+bc)i=(ac-bd,(ab+bc)i)
{