由于作者水平就只能到模板,所以笔记也就写到这里。
FFT
简介
FFT是干啥的?它是用来加速多项式乘法的。我们平时经常求多项式乘法,比如(x+1)(x+3)=(x2+4x+3)(x+1)(x+3)=(x2+4x+3)。假设两个式子都是nn项(不足的补0),那朴素的算法是O(n2)O(n2)的。
那么,我们能做到O(nlogn)O(nlogn)做么?
你也许觉得不珂能。的确,不看题解,是很难自己想到正解的(除非你是欧拉费马级别的天才)
前置知识
多项式点值表示
我们平常表达多项式,都是用系数表达的。当然,还有点值表达。用平面直角坐标系上的nn个点,唯一确定一个(不超过)n−1n−1次的多项式。它的一个特殊形式,就是两点确定一条直线。
点值转系数表达,你只需要解一个方程组就珂以了。(高斯消元,这样是O(n3)O(n3)的)
复数
复数的定义
这个很多人知道。定义i=√−1i=√−1,即虚数单位。形如a+bia+bi的数就是复数(complex)。
复数a+bia+bi的辐角:从(0,0)(0,0)到(a,b)(a,b)的线段和xx轴的夹角。这个夹角,是顺时针方向的夹角。取值范围是[0,360][0,360]。
复数的几何意义
在一维数轴上,我们把一个数乘以−1−1,相当于旋转了180180。
那么,我们把一个数乘以√−1√−1,相当于:乘两次是旋转180180。所以,乘一次就是旋转9090,也就是竖起来了。“竖起来”,这个概念很好表示,就是yy坐标。
那么,a+bia+bi就相当于平面直角坐标系上的点(a,b)(a,b)。当然,你也珂以把它看成一个向量,从(0,0)(0,0)到(a,b)(a,b)。
复数的运算
复数的加法:(a1+b1i)+(a2+b2i)=(a1+a2)+(b1+b2)i(a1+b1i)+(a2+b2i)=(a1+a2)+(b1+b2)i。(这样也就包含了减法的情况)
复数的乘法:(a1+b1i)×(a2+b2i)(a1+b1i)×(a2+b2i)
我们把括号拆开,然后把i2i2都变成−1−1(由i的定义)。易得,它等于:(a1a2−b1b2)+(a1b2+a2b1)i
(n次)单位复根
满足xn=1的所有复数解中,辐角最小且不为0的那个复数。记作wn。不难发现,所有满足条件的x,用向量表示后,把单位圆n等分。
举个栗子,n=6的时候,解的分布是这样的:
其中,ωn就是图中标出来的,辐角大于0且最小的那个向量。
单位复根的性质
- w2k2n=wkn(珂以把wkn看成是360deg×kn,这条证毕)
- wk+n/2n=−wkn。(这里n/2不取整,就是小数) (把n/2看成是转半圈,转半圈也就是x,y坐标都变负,这条也证毕)
正式开始!
上面不是说了点值表示么。对于两个用点值表示的多项式,只要把对应的点值乘起来即珂。
但是,我们要取n个点(DFT),每次O(n)求值,不是要O(n2)了么?
而且,把点值转换成系数(插值,IDFT)的过程,不是要O(n3)么?
因此,我们的主干思想是:利用单位复根的性质,巧妙的求值/插值,使得我们在O(nlogn)的时间内完成这些操作。
简图(远航之曲大佬的图):
DFT
就是快速带入点值的过程。
我们的多项式:A(x)=a0x0+a1x1+a2x2…+an−1xn−1。其中a0,a1…an是系数(题目给定)。
接下来,我们设n=2k。不足的地方用0补齐。
把它的系数按下表奇偶分组(指数还是顺序下来的):
A0(x)=a0x0+a2x1+a4x2…+an−2xn/2−1
A1(x)=a1x0+a3x1+a5x2…+an−1xn/2−1
易得A(x)=A0(x2)+xA1(x2)。
那么,我们代入x=w0n,wn,w2n…wn−1n。
考虑求前面n/2个,然后直接得到后面n/2个。令k∈[0,n/2),则:
A(wkn)=A0(w2kn)+wknA1(w2kn)
然后我们再代入wk+n/2n=−wkn:
A(wk+n/2n)=A0(w2nk)−A1(w2nk)
我们发现,wk+n/2n=−wkn。然而A0,A1里面是x2,所以取负不影响A0和A1的结果,只有A1前面那一项有一个正负号的区别!
所以,我们求出前一半,就珂以O(n)求出后一半。
这样显然是O(nlogn)的。
DFT的实现优化
刚刚做完O(nlogn)的式子。但是,实现的时候,递归似乎太慢了,还不如暴力来的快。
我们观察一下,被奇偶分组后的下标。
(草 图)
转换一下二进制:
000 001 010 011 100 101 110 111
变成:
000 100 010 110 001 101 011 111
相当于每个二进制数位反过来了。
然后我们通过递推,推出最后的状态。然后不断合并,合并成答案。成功的把递归转化掉了。这样还是O(nlogn),但是快了很多!
IDFT
IDFT,就是我们已知一个点值表示的多项式,而且代入的点值还是w0n,w1n…wn−1n。
我们设出系数a0,a1..an−1,列出方程:
{a0(w0n)0+a1(w0n)1...+an−1(w0n)n−1=A(w0n)a0(w1n)0+a1(w1n)1...+an−1(w1n)n−1=A(w1n)...a0(wn−1n)0+a1(wn−1n)1...+an−1(wn−1n)n−1=A(wn−1n)用矩阵表达:
[(w0n)0(w0n)1...(w0n)n−1(w1n)0(w1n)1...(w1n)n−1...(wn−1n)0(wn−1n)1...(wn−1n)n−1]×[a0a1...an−1]=[A(w0n)A(w1n)...A(wn−1n)]设这个式子是“珂朵莉IDFT①式”
然后设矩阵D,D中的每一项和左边那个矩阵
中对应位置上的项,都是倒数。
已证,D×V=n×In,其中In是n阶单位矩阵。
那也就是,D=1nV−1
把“珂朵莉IDFT①式”中,左右两边同时乘一个D=1nV−1。
易得:
那么我们把矩阵V中,所有项取倒数,然后做一遍DFT即珂。最后记得除一个n。
模板题的代码
洛谷 3803
1 |
|
NTT
NTT本质上就是把FFT中的单位复根换成一个有相同性质的整数:原根。
只要记住998244353的原根是3即珂。
然后和FFT同样的方法去写就珂以了,也是DFT+IDFT。
就是把里面单位复根换成原根,一样写即可。
代码:
1 |
|
NTT的好处和坏处
好处: 和FFT相比,把浮点数换成整数。常数很小,而且避免了精度问题
坏处: 小心溢出!(超过NTT模数也是一种溢出,会导致答案不同)(如果让你对某个数取模那另说)