概述
这篇博客是拿来解释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,n−1]的位置上。
对于
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=−ωnn−i
刚好是我们需要的变换,这样我们就只需要一半的单位根即可完成
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代码技巧所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复