FFT(快速傅里叶变换)/NTT(数论变换)是卷积运算常见而实用的优化
但是FFT/NTT的处理过程并不像暴力运算(差不多是多项式乘法)那样能够直观地反映卷积结果的实时变化。
因此在使用FFT时将会或多或少地加大调试的难度。
如果调试程序时直接跟踪变量,每步手算结果比对,不仅会耽误大量时间,而且效果可能并不理想。
直接肉眼查错效率可能也不太高。
但也正由于FFT的特点,实际上也有一些很方便而实用的调试方法,能够明显提升调试的效率,减小调试难度。
一 、暴力卷积 确定算法
先写好一个能解决题目问题的程序,只不过求解卷积的过程使用暴力来实现
或许这一步看上去是很浪费时间的,但是这一步也是很有用的
在做着一步时,我们可以将思维的重点放在建模和模型变换上。建模也是写完 FFT模板之后必做的过程,在这一开始就做好建模不仅可以减少打好FFT模板之后的思考复杂度和紧张感,而且也便于直观地发现和更正模型的错误。
如果在这一步就确定了卷积算法不可行,那么就之后就不用再写FFT多走弯路
如果在这一步确定的卷积算法的正确性,那么在之后程序出错时,我们就可以大胆地排除算法的错误性,将调试的重点放在FFT模板上。
这一步写好的程序还可以在后面用来对拍
在比赛中,如果花了不短的时间写好了FFT模板,结果发现套用卷积算法并没有想象中的那么简单,甚至卷积根本就解决不了题目。那么这时整个人肯定都不太好受吧。。。
为了比赛打得稳,最好还是先写一个卷积暴力。
如果不想写暴力卷积代码,其实我们还可以直接暴力手算卷积,看能不能得出样例的结果
怎么方便地手算呢?
机械地执行卷积代码..?
直接模仿竖式乘法,将两个输入数组移位相加就好啦
二 、分段查错 快速定位
在平常的调试中,时间肯定会浪费在正确代码的检验上
而如果程序比较长,我们可能还会在定位错误的位置中耽误很多时间
因此为了调试过程的高效快速
调试时就可以缩小调试的范围
直接依次检测每一个具有完整功能的子程序的错误
三 、输出调试 验证模板
我们甚至可以在写代码时每写好一段就输出运算结果,进一步提升写题的效率
而FFT的特性也方便我们写FFT模板时的分段输出调试
0检查曾经写错的代码,关键代码是否写错 数组大小是否足够
1检查位数n,模数p=kn+1,次数ci,原根g,n的逆元(乘n是否为1)是否正确
3接着检查n次单位根w是否正确
怎么检查呢..?直接检查w[1]^n是否为1就好啦
4检查二进制反序的处理是否正确
打好模板了,上面这些关键部分都没问题,但这样还不能保证FFT完全无错
接下来的操作挺简单而有用的,通过了这个验证则说明FFT很可能无错
5先将要进行多项式乘法数组a的DFT,再求出这个结果的逆DFT,观察是否等于a本身
接下来还可以和之前写的暴力程序对拍,保证程序的正确性
四 、补充
FFT用到了对中学生来说相对高深的数学知识
如果不能弄懂原理死记硬背起来应该相当麻烦
那么有什么记模板的方法呢..?
1最主要的还是要弄懂原理,理解算法。可以尝试补充学习一些数学知识和阅读一些相关论文
2记录下模板的要点
大致弄清有几个部分 有几层循环 每一部分大致要做什么
3 多写模板,并且总结容易写错的地方
4 编一些有趣的口诀记忆
做题时常用的方法:
1答案对小素数取模,且n的数量级在10^5时,可以直接进行FFT,然后将结果加上eps后进行取整操作
2答案对大素数取模,若大素数(约10^9数量级)为二的幂的若干倍+1(kn+1),那么可以直接进行NTT
如果大素数不是kn+1的形式
那么可以选取3个合适的为kn+1形式的素数,然后分别求解答案
再使用中国剩余定理求得模原大素数的答案