缩写 | 全称 | 作用 | 时间复杂度 |
---|---|---|---|
DFT | 离散傅立叶变换 | 时频域转换 | O ( n 2 ) O(n^2) O(n2) |
FFT | 快速傅立叶变换 | 时频域转换 ( ( (有精度误差 ) ) ) | O ( 大 常 数 + n l o g 2 n ) O(大常数+nlog_2n) O(大常数+nlog2n) |
NTT/FNTT | 快速数论变换 | 模意义下的时频域转换 | O ( 小 常 数 + n l o g 2 n ) O(小常数+nlog_2n) O(小常数+nlog2n) |
MTT | 任意模数的NTT | 任意模意义下的时频域转换 | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) |
FWT | 快速沃尔什变换 | 快速集合卷积 | O ( 不 定 ) O(不定) O(不定) |
FMT | 快速莫比乌斯变换 | 逆莫比乌斯反演? | O ( 不 定 ) O(不定) O(不定) |
% 本文不包含但必不可少的前置技能:
% 由于 FFT 涉及到复数运算,难免有精度问题,而且有的时候精度还不小,这便让我们考虑是否有在模意义下快速计算的方法,这就是快速数论变换 ( F a s t N u m b e r − T h e o r e t i c T r a n s f o r m , F N T ) (Fast Number-Theoretic Transform,FNT) (FastNumber−TheoreticTransform,FNT)
% 仔细理解FFT,你会发现,整个FFT用的是单位根的五大性质。
(数论)阶 对于 ( a , n ) = 1 (a,n)=1 (a,n)=1 的整数,满足 a r ≡ 1 ( m o d n ) a^r≡1 \pmod n ar≡1(modn) 的最小整数 r r r,称为 a a a 模 n n n 的 (数论)阶。
原根 对于正整数 n n n,整数 a a a,若 a a a 模 n n n 的阶 r r r 等于 φ ( n ) φ(n) φ(n),则称 a a a 为模 n n n 的一个原根。
% 感性地理解一下,一个整数 n n n 的数论阶为其最小开始循环的次方数(且与 n n n 互质)。若一个正整数 a a a 模 p p p 的数论阶恰好为 φ ( n ) \varphi(n) φ(n) ,则 a a a 为 p p p 的原根。
% 根据原根的定义可得,对于质数 p p p,假如 g g g 是 p p p 的原根,则 g 0 ≡ g φ ( p ) ≡ 1 ( m o d p ) g^0≡g^{\varphi(p)}≡1\pmod p g0≡gφ(p)≡1(modp),进一步可以得到,对于任意 a ≢ b ( m o d φ ( p ) ) a\not≡ b\pmod {\varphi(p)} a≡b(modφ(p)),有 g a ≢ g b ( m o d p ) g^a\not ≡g^b\pmod p ga≡gb(modp)。
% 对于质数 p = k ⋅ 2 N + 1 p=k\cdot2^N+1 p=k⋅2N+1,设其原根为 g g g。我们令 g n ≡ g p − 1 n ( m o d p ) {g_n≡g^{\frac{p-1}{n}}\pmod p} gn≡gnp−1(modp)。注意,这里的 g n g_n gn 是定义出来的,只是在数值上等于 g p − 1 n g^{\frac {p-1}{n}} gnp−1,与 g g g 没有关系。容易发现,这样的定义使得 g n g_n gn 满足了第一条、第二条和第三条性质。
性质1证明:
% 由于 g n n ≡ ( g p − 1 n ) n ≡ g p − 1 ≡ 1 ≡ g n 0 ( m o d p ) {g^n_n≡\Big(g^{^{p-1\over n}}\Big)^n≡g^{p-1}≡1≡g_n^0\pmod p} gnn≡(gnp−1)n≡gp−1≡1≡gn0(modp) % 因此其满足性质1。
性质2证明:
% 由于 p ∈ prime p\in \text{prime} p∈prime,因而 φ ( p ) = p − 1 \varphi(p)=p-1 φ(p)=p−1,因此对于所有 a ≢ b ( m o d ( p − 1 ) ) {a\not≡ b\pmod {(p-1)}} a≡b(mod(p−1)),有 g a ≢ g b ( m o d p ) {g^a\not≡ g^b\pmod p} ga≡gb(modp)。
% 因此对于所有 a ≢ b ( m o d ( p − 1 ) ) {a\not≡ b\pmod {(p-1)}} a≡b(mod(p−1)),有 g n a ≡ ( g p − 1 n ) a ≢ ( g p − 1 n ) b ≡ g n b ( m o d p ) {g_n^a≡(g^{\frac{p-1}{n}})^a\not≡ (g^{\frac{p-1}{n}})^b≡g_n^b\pmod p} gna≡(gnp−1)a≡(gnp−1)b≡gnb(modp) 因此其满足性质2。
性质3证明:
% 由于 g 2 n 2 k ≡ ( g p − 1 2 n ) 2 k ≡ ( g p − 1 n ) k ≡ g n k ( m o d p ) {g^{2k}_{2n}≡(g^{p-1\over 2n})^{2k}≡(g^{p-1\over n})^{k}}≡g_n^k\pmod p g2n2k≡(g2np−1)2k≡(gnp−1)k≡gnk(modp) % 因此其满足性质3。
性质4证明:
% 由于 p p p 是质数,并且 g n n ≡ 1 ( m o d p ) {g_n^n \equiv 1 \pmod p} gnn≡1(modp),因而有
g n n 2 ≡ 1 ( m o d p ) or g n n 2 ≡ − 1 ( m o d p ) {g^{\frac n2}_n≡1\pmod p}\quad \text{or}\quad {g^{\frac n2}_n≡-1\pmod p} gn2n≡1(modp)orgn2n≡−1(modp)
% 由第二条性质可得 g n n 2 ≢ g n n ≡ 1 ( m o d p ) {g^{\frac n2}_n\not≡ g_n^{n} ≡1\pmod p} gn2n≡gnn≡1(modp),因而舍去前者,取后者,因而有 g n k + n 2 ≡ g n k ⋅ g n n 2 = − g n k ( m o d p ) g_n^{k+\frac n2}≡g_n^k\cdot g_n^{\frac n2}=-g_n^k\pmod p gnk+2n≡gnk⋅gn2n=−gnk(modp)
% 因此其满足性质4。
性质5证明:
% 当 k ≠ j k\ne j k=j 时,根据等比数列的求和公式,可得:
∑ i = 0 n − 1 ( g n j − k ) i = ( g n k ) n − 1 g n k − 1 = ( g n n ) k − 1 g n k − 1 = 1 − 1 g n k − 1 = 0 \begin{aligned}\sum_{i=0}^{n-1}(g_n^{j-k})^i =&\dfrac{(g_n^k)^{n}-1}{g_n^k-1}\\ =&\dfrac{(g_n^n)^{k}-1}{g_n^k-1}\\ =&\dfrac{1-1}{g_n^k-1}\\ =&\ 0\\ \end{aligned} i=0∑n−1(gnj−k)i====gnk−1(gnk)n−1gnk−1(gnn)k−1gnk−11−1 0
% 当 k = j k=j k=j 时,可得 ∑ i = 0 n − 1 ( g n j − k ) i = ∑ i = 0 n − 1 1 = n \begin{aligned}\sum\limits_{i=0}^{n-1}(g_n^{j-k})^i =\sum\limits_{i=0}^{n-1}\ 1=n\end{aligned} i=0∑n−1(gnj−k)i=i=0∑n−1 1=n
% 综上,有 ∑ i = 0 n − 1 ( g n j − k ) i = { 0 , k ≠ j n , k = j \begin{aligned}\sum\limits_{i=0}^{n-1}(g_n^{j-k})^i =\begin{cases}0,k\ne j\\n,k=j\\\end{cases}\end{aligned} i=0∑n−1(gnj−k)i={0,k=jn,k=j
% 至此,我们发现 g n g_n gn 满足 w n w_n wn 的五条性质,因而我们可以大方地用 g n g_n gn 代替 w n w_n wn。
% 为什么模数必须取 k ⋅ 2 N + 1 k\cdot2^N+1 k⋅2N+1?注意到我们取 g n ≡ g p − 1 n ( m o d p ) {g_n≡g^{\frac{p-1}{n}}\pmod p} gn≡gnp−1(modp),因而 p − 1 n \dfrac {p-1}{n} np−1 必须是整数。考虑到之前FFT的过程是不断地二分,因而我们可以保证 n n n 总是 2 2 2 的倍数,因而模数必须有足够多的质因子 2 2 2,因而取 k ⋅ 2 N + 1 k\cdot2^N+1 k⋅2N+1,且 N N N 必须足够大。
% 但这也注定了FNT的一个弊端,对于如果 r ⋅ 2 N + 1 r⋅2^N+1 r⋅2N+1 是个素数,那么在模 k ⋅ 2 N + 1 k⋅2^N+1 k⋅2N+1 意义下,只可以处理 2 N 2^N 2N 以内规模的数据。对于一些好的质数及其原根见这里。
% 在逆 F N T FNT FNT 的时候,我们需要求 g n − 1 g_n^{-1} gn−1,在模意义下,也就是 g n g_n gn 的逆元,预处理即可。
Accepted -O2 \text{Accepted -O2} Accepted -O2 / 用时: 1925 m s 1925ms 1925ms / 内存: 243752 KB 243752\text{KB} 243752KB
#include
using namespace std;
#define LL long long
const int maxn=3*1e6+10,P=998244353,G=3,inv_G=332748118;
int N,M,limit,L,r[maxn];
LL a[maxn],b[maxn];
inline LL fastpow(LL a,LL k) {
LL base=1;
while(k) {
if(k&1) base=(base*a)%P;
a=(a*a)%P;
k>>=1;
} return base%P;
}
#define inv(x) fastpow(x,P-2)
inline void NTT(LL *A,int type) {
for(int i=0;i<limit;i++)
if(i<r[i]) swap(A[i],A[r[i]]);
for(int mid=1;mid<limit; mid <<= 1) {
LL Wn=fastpow(type==1?G:inv_G,(P-1)/(mid<<1));
for(int j=0;j<limit;j+=(mid<<1)) {
LL w=1;
for(int k=0;k<mid;k++,w=(w*Wn)%P) {
int x=A[j+k],y=w*A[j+k+mid]%P;
A[j+k]=(x+y)%P,
A[j+k+mid]=(x-y+P)%P;
}
}
}
}
int main() {
scanf("%d%d",&N,&M);
for(int i=0;i<=N;i++)
scanf("%d",&a[i]),a[i]=(a[i]+P)%P;
for(int i=0;i<=M;i++)
scanf("%d",&b[i]),b[i]=(b[i]+P)%P;
for(limit=1,L=0;limit<=N+M;limit<<=1,L++);
for(int i=0;i<limit;i++)
r[i]=(r[i>>1]>>1)|((i&1)<<(L-1));
NTT(a,1);NTT(b,1);
for(int i=0;i<limit;i++)
a[i]=(a[i]*b[i])%P;
NTT(a,-1);
LL inv=inv(limit);
for(int i=0;i<=N+M;i++)
printf("%d ",(a[i]*inv)%P);
return 0;
}
可以发现,FNT和FFT的效率差别还是很大的,FNT完美碾压FFT。
% FNT虽快,但使用局限太多,当模数不为 k ⋅ 2 N + 1 k⋅2^N+1 k⋅2N+1 时,FNT便无法发挥其效果,有没有解决办法呢?当然有。(坑)
%https://www.hrwhisper.me/introduction-to-simplex-algorithm/