我是靠谱客的博主 酷炫蜜粉,最近开发中收集的这篇文章主要介绍FFTNTT代码技巧,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

这篇博客是拿来解释FFT模板中那些过度压行的产物的。
1.

int& upd(int &x){ return x+=x>>31&mod; }

所有负数 x > > 31 x>>31 x>>31后为 − 1 -1 1
所以这个是快速将负数加上一个 m o d    mod {} mod
为啥搞负数?
因为我们可以在每次加法后 − m o d -mod mod,减法后不管,然后调用这个函数。
这个也可以用在乘负数,模完之后再把负的答案调回正的,
反正大家的加法取模优化都要非负环境。

2.预处理单位根

	w[Wl] = 1;
	rep(i,Wl+1,Wl2-1) w[i] = 1ll * w[i-1] * pw % mod;
	per(i,Wl-1,1) w[i] = w[i<<1];

因为就算我们需要n次单位根,但是只需要一半。
所以我们就把 n n n次单位根存在 [ n 2 , n − 1 ] [frac n2,n-1] [2n,n1]的位置上。
对于 n 2 frac n2 2n次单位根等也同理。

3.NTT的 u n s i g n e d   l o n g   l o n g rm unsigned long long unsigned long long 优化

void NTT(int *A,int n,int tp){
	static int r[maxn];
	static unsigned long long a[maxn];
	if(tp^1) reverse(A+1,A+n);
	rep(i,0,n-1) r[i] = r[i>>1]>>1|(i&1)<<lg[n]-1,a[i] = (A[r[i]] += A[r[i]] >> 31 & mod);
	for(int L=1;L<n;L<<=1) for(int s=0,L2=L<<1;s<n;s+=L2) for(int k=s,x=L,t;k<s+L;k++,x++)
		t=w[x]*a[k+L]%mod,a[k+L]=a[k]-t+mod,a[k]+=t;
	rep(i,0,n-1) A[i] = a[i] % mod;
	if(tp^1) rep(i,0,n-1) A[i] = 1ll * A[i] * inv[n] % mod; 
}

首先如果是 I D F T IDFT IDFT,我们可以翻转数组后做,这是因为 ω n i = − ω n n − i omega_n^i = -omega_n^{n-i} ωni=ωnni
刚好是我们需要的变换,这样我们就只需要一半的单位根即可完成 D F T DFT DFT
多写写 F F T FFT FFT就可以发现是可以简化到循环内只有3条加减乘除语句的。
一次乘法,两次加减。
可以使用函数优化加减取模,但这还是不够快。
可以使用 u n s i g n e d   l o n g   l o n g rm unsigned long long unsigned long long后加减完全不模。
但是这个方法有点险但是实际上很稳。
因为模数都是 998244353 998244353 998244353
分析一下加减对于每个数都只有 O ( log ⁡ n ) O(log n) O(logn)次,我们把这个数字在下面估计为 20 20 20
那么最坏情况,
在乘法的时候,可以造成一个 998244353 × ( 998244353 log ⁡ n ) = 19929835765927772180 > 2 64 = 18446744073709551616 998244353 times (998244353 log n) = 19929835765927772180 > 2^{64} = 18446744073709551616 998244353×(998244353logn)=19929835765927772180>264=18446744073709551616
位数太多不直观,我们取个 log ⁡ 10 log_{10} log10
log ⁡ 10 19929835765927772180 = 19.29950371986229427535856388548 log_{10}19929835765927772180 =19.29950371986229427535856388548 log1019929835765927772180=19.29950371986229427535856388548
log ⁡ 10 18446744073709551616 = 19.265919722494796493679289262368 log_{10}18446744073709551616 = 19.265919722494796493679289262368 log1018446744073709551616=19.265919722494796493679289262368
毫厘之差。
但是,单位根是均匀分布的。
F F T FFT FFT的蝴蝶变换是复杂的。
所以平均情况下大概的平均数值要在上面的基础上 ÷ 4 div 4 ÷4,也就是开 l o n g   l o n g rm long long long long就行的范围。
但是这只是平均, n n n个位置都经过 log ⁡ log log轮变换后炸 l o n g   l o n g rm long long long long的概率还是很高的( n = 10000 n=10000 n=10000都能炸)。
最坏情况的平均值应该是在 19929835765927772180 ÷ 2 19929835765927772180 div 2 19929835765927772180÷2
因为每次加减的数有均匀的单位根加权,大概只会加一半。
但是经过实际测试最坏情况的测试最大值和 n n n大概有如下关系:
n = 10000 n = 10000 n=10000时 , 1.1 × 2 63 1.1 times 2^{63} 1.1×263
n = 100000 n = 100000 n=100000时 , 1.3 × 2 63 1.3times 2^{63} 1.3×263
n = 1000000 n = 1000000 n=1000000时 , 1.6 × 2 63 1.6times 2^{63} 1.6×263
再大就超过 F F T FFT FFT的适用范围了。
注意这个是最坏情况的测试最大值,也就是随机了很多组也构造了很多组(但是构造的数据并没有比随机的强很多)后最坏情况的最大值。
实际上用这个方法写个仙人掌计数跑 1 e 5 1e5 1e5可以跑进 0.5 s 0.5s 0.5s
有图为证,目前为 r k 2 rk2 rk2
在这里插入图片描述
实际上理论最坏的情况比我们的最大容忍值只大了一丁点,实际运行(我真的认为)毫无问题。
事实上 2 64 99824435 3 2 = 18.51168699066378905130903454712 frac {2^{64}}{998244353^2} = 18.51168699066378905130903454712 9982443532264=18.51168699066378905130903454712
这, 1 e 5 1e5 1e5是不会出任何问题的,只要你不把 > 998244353 >998244353 >998244353的数拿进去 N T T NTT NTT

大于 3 e 5 3e5 3e5的多项式题在如今高级算法迭起,动不动 O ( n log ⁡ n ) O(nlog n) O(nlogn) 3 s 3s 3s O I OI OI界是真的不会有太多的。
所以我会一直用这个方法来写我的多项式题。

最后

以上就是酷炫蜜粉为你收集整理的FFTNTT代码技巧的全部内容,希望文章能够帮你解决FFTNTT代码技巧所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(56)

评论列表共有 0 条评论

立即
投稿
返回
顶部