前言
算法简介
上次的FFT大量使用浮点数,有精度不好控制的问题,使用可以保证精度的方法又容易超时。这里NTT是在取模的一个前提下找到一个新的且为整数的单位根(在取模意义下),我们记其为
G
G
G,那么用这个根去替代原来的
ω
n
\omega_n
ωn来进行运算。因为所有运算就会变成整数运算,所以精度误差大大降低。使用NTT也方便按照题目要求进行取模运算,缺点就是只能进行以整数为系数的多项式运算,同时数组长度和每一项的值(如果题目原本不要求取模的话)都有限制。
前置知识
阶
若正整数
a
a
a,
p
p
p互质且满足
p
>
1
p>1
p>1,则:
对于
a
n
≡
1
(
m
o
d
p
)
a^n \equiv 1(\mod p )
an≡1(modp)最小的
n
n
n,我们称之为
a
a
a模
p
p
p的阶,记作
δ
p
(
a
)
\delta_p(a)
δp(a)。
对于
i
∈
[
0
,
δ
p
(
a
)
)
i \in [0,\delta_p(a))
i∈[0,δp(a)),所有的
a
i
m
o
d
p
a^i \mod p
aimodp结果互不相同。
我们假设存在两个整数
j
j
j,
k
k
k满足
a
j
≡
a
k
(
m
o
d
p
)
,
0
≤
j
<
k
<
δ
p
(
a
)
a^j \equiv a^k (\mod p),0 \le j \lt k \lt \delta_p(a)
aj≡ak(modp),0≤j<k<δp(a)则有
a
k
−
j
≡
1
(
m
o
d
p
)
a^{k-j}\equiv 1 (\mod p)
ak−j≡1(modp),与阶的定义矛盾。
原根
设
n
n
n是正整数,
g
g
g是整数,若
g
g
g模
n
n
n的阶
δ
n
(
g
)
=
φ
(
n
)
\delta_n(g)= \varphi(n)
δn(g)=φ(n)且
gcd
(
g
,
n
)
=
1
\gcd(g,n) =1
gcd(g,n)=1,则称
g
g
g为
n
n
n的一个原根。
若
gcd
(
g
,
n
)
=
1
\gcd(g,n) =1
gcd(g,n)=1且
n
>
0
n>0
n>0,则
g
g
g为
n
n
n的一个原根的充要条件为:
S
=
{
g
1
,
g
2
,
g
3
,
⋯
,
g
φ
(
n
)
}
S=\{g^1,g^2,g^3,\cdots,g^{\varphi(n)} \}
S={g1,g2,g3,⋯,gφ(n)}为
n
n
n的一组简化剩余系(即一组包含所有模
n
n
n后与
n
n
n互质且取模后的数两两不相同的数)。
由上文可知若
g
g
g为原根,则
S
S
S中任意两个元素在模
n
n
n的意义下不同余。
又因为
gcd
(
g
,
n
)
=
1
\gcd(g,n)=1
gcd(g,n)=1,故而
S
S
S为
n
n
n的一组简化剩余系。
FFT
FFT详解
正文
关于原根和模数的选择
由于NTT大量二分数组,我们希望选一些形如
p
=
q
×
2
k
+
1
p=q \times 2^k + 1
p=q×2k+1的质数
p
p
p,其中
q
q
q为奇自然数,
k
k
k为整数。
我们有以下的原根可以选。
模数 | 原根 | 原根的逆元 | 最大长度 |
---|---|---|---|
469762049 = 7 × 2 26 + 1 469762049=7 \times 2^{26} + 1 469762049=7×226+1 | 3 | 156587350 | 2 26 2^{26} 226 |
998244353 = 119 × 2 23 + 1 998244353=119 \times 2^{23} + 1 998244353=119×223+1 | 3 | 332748118 | 2 23 2^{23} 223 |
2281701377 = 17 × 2 27 + 1 2281701377=17 \times 2^{27} + 1 2281701377=17×227+1 | 3 | 760567126 | 2 27 2^{27} 227 |
1004535809 = 479 × 2 21 + 1 1004535809=479 \times 2^{21} + 1 1004535809=479×221+1 | 3 | 334845270 | 2 21 2^{21} 221 |
DFT
通过观察,我们可以发现原根有一些和复数单位根很像的性质:
我们令
n
n
n为大于
1
1
1的
2
2
2的幂,
p
p
p为素数且
n
∣
(
p
−
1
)
n |(p-1)
n∣(p−1),
g
g
g为
p
p
p的一个原根。
我们设
g
n
=
g
p
−
1
n
g_n=g^{\frac{p-1}{n}}
gn=gnp−1所以
g
n
n
=
g
n
×
p
−
1
n
=
g
p
−
1
g_n^n=g^{n \times \frac{p-1}{n}}=g^{p-1}
gnn=gn×np−1=gp−1
g
n
n
2
=
g
p
−
1
2
g_n^{\frac{n}{2}}=g^{\frac{p-1}{2}}
gn2n=g2p−1
g
a
n
a
k
=
g
a
k
(
p
−
1
)
a
n
=
g
k
(
p
−
1
)
n
=
g
n
k
g_{an}^{ak}=g^{\frac{ak(p-1)}{an}}=g^{\frac{k(p-1)}{n}}=g_n^k
ganak=ganak(p−1)=gnk(p−1)=gnk通过观察不难得出:
g
n
n
≡
g
p
−
1
≡
1
(
m
o
d
p
)
g_n^n \equiv g^{p-1} \equiv 1 (\mod p)
gnn≡gp−1≡1(modp)又因为
(
g
n
n
2
)
2
≡
1
(
m
o
d
p
)
(g_n^{\frac{n}{2}})^2\equiv 1 (\mod p)
(gn2n)2≡1(modp),且同属于一个简化剩余系,不能相等,
故
g
n
n
2
≡
−
1
(
m
o
d
p
)
g_n^{\frac{n}{2}}\equiv -1 (\mod p)
gn2n≡−1(modp)同时我们有
(
g
n
k
+
n
2
)
2
≡
(
g
n
k
)
2
×
g
n
n
≡
(
g
n
k
)
2
(
m
o
d
p
)
(g_n^{k + \frac{n}{2}})^2\equiv (g_n^k)^2 \times g_n^n \equiv (g_n^{k})^2 (\mod p)
(gnk+2n)2≡(gnk)2×gnn≡(gnk)2(modp)又因为同属于一个简化剩余系,不能相等,可得
g
n
k
+
n
2
≡
−
g
n
k
(
m
o
d
p
)
g_n^{k + \frac{n}{2}} \equiv -g_n^{k} (\mod p)
gnk+2n≡−gnk(modp)
总而言之,单位根在FFT中用到的性质原根都有。
g
n
n
≡
1
(
m
o
d
p
)
g_n^n \equiv 1 (\mod p)
gnn≡1(modp)
g
a
n
a
k
≡
g
n
k
(
m
o
d
p
)
g_{an}^{ak} \equiv g_n^k(\mod p)
ganak≡gnk(modp)
g
n
n
2
≡
−
1
(
m
o
d
p
)
g_n^{\frac{n}{2}}\equiv -1 (\mod p)
gn2n≡−1(modp)
g
n
k
+
n
2
≡
−
g
n
k
(
m
o
d
p
)
g_n^{k + \frac{n}{2}} \equiv -g_n^{k} (\mod p)
gnk+2n≡−gnk(modp)
所以在DFT的过程中带入
g
n
k
g_n^k
gnk和
g
n
k
+
n
2
g_n^{k+\frac{n}{2}}
gnk+2n实质上和带入
ω
n
k
\omega_n^k
ωnk和
ω
n
k
+
n
2
\omega_n^{k+\frac{n}{2}}
ωnk+2n一样。
IDFT
和FFT一样,用矩阵来辅助思考:
[
g
n
0
g
n
0
g
n
0
⋯
g
n
0
g
n
0
g
n
1
g
n
2
⋯
g
n
n
−
1
g
n
0
g
n
2
g
n
4
⋯
g
n
2
n
−
2
g
n
0
g
n
3
g
n
6
⋯
g
n
3
n
−
3
⋮
⋮
⋮
⋱
⋮
g
n
0
g
n
n
−
1
g
n
2
n
−
2
⋯
g
n
(
n
−
1
)
2
]
∗
[
a
0
a
1
a
2
a
3
⋮
a
n
−
1
]
=
[
F
0
F
1
F
2
F
3
⋮
F
n
−
1
]
\left[ \begin{array}{ccccc} g_{n}^{0} &g_{n}^{0}&g_{n}^{0} & \cdots & g_{n}^{0} \\ g_{n}^{0}&g_{n}^{1}&g_{n}^{2} & \cdots & g_{n}^{n-1} \\ g_{n}^{0} & g_{n}^{2} & g_{n}^{4} & \cdots &g_{n}^{2n-2} \\ g_{n}^{0} &g_{n}^{3} & g_{n}^{6} &\cdots & g_{n}^{3n-3} \\ \vdots & \vdots & \vdots &\ddots & \vdots \\ g_{n}^{0} &g_{n}^{n-1} &g_{n}^{2n-2} &\cdots &g_{n}^{(n-1)^2}\\ \end{array} \right] * \left[ \begin{array}{ccccc} a_0\\ a_1\\ a_2\\ a_3\\ \vdots\\ a_{n-1}\\ \end{array} \right] = \left[ \begin{array}{ccccc} F_0\\ F_1\\ F_2\\ F_3\\ \vdots\\ F_{n-1}\\ \end{array} \right]
⎣⎢⎢⎢⎢⎢⎢⎢⎡gn0gn0gn0gn0⋮gn0gn0gn1gn2gn3⋮gnn−1gn0gn2gn4gn6⋮gn2n−2⋯⋯⋯⋯⋱⋯gn0gnn−1gn2n−2gn3n−3⋮gn(n−1)2⎦⎥⎥⎥⎥⎥⎥⎥⎤∗⎣⎢⎢⎢⎢⎢⎢⎢⎡a0a1a2a3⋮an−1⎦⎥⎥⎥⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎢⎢⎢⎡F0F1F2F3⋮Fn−1⎦⎥⎥⎥⎥⎥⎥⎥⎤
和FFT不太一样,乘以单位根的共轭复数的过程将变成乘以原根在模
p
p
p意义下的逆元,最后再乘以长度
n
n
n模
p
p
p下的逆元即可。
证明如下(以下运算均在模
p
p
p的条件下进行):
g
n
k
=
g
(
p
−
1
n
)
k
g_n^k=g^{(\frac{p-1}{n})k}
gnk=g(np−1)k
如上矩阵,
F
(
k
)
=
∑
j
=
0
n
−
1
a
(
j
)
(
g
n
i
)
j
k
F(k)=\sum_{j=0}^{n-1}a(j) (g_n^{ i })^{jk}
F(k)=j=0∑n−1a(j)(gni)jk
逆变换为:
1
n
∑
k
=
0
n
−
1
F
(
k
)
(
g
n
i
)
−
j
k
=
1
n
∑
k
=
0
n
−
1
(
∑
l
=
0
n
−
1
a
(
l
)
(
g
n
i
)
l
k
)
(
g
n
i
)
−
j
k
=
1
n
∑
k
=
0
n
−
1
(
∑
l
=
0
n
−
1
a
(
l
)
(
g
n
i
)
(
l
−
j
)
k
)
\frac{1}{n} \sum_{k=0}^{n-1}F(k) (g_n^{ i })^{ -jk }=\frac{1}{n} \sum_{k=0}^{n-1}( \sum_{l=0}^{n-1}a(l)(g_n^{ i })^{ lk } )(g_n^{ i })^{- jk }=\frac{1}{n} \sum_{k=0}^{n-1}( \sum_{l=0}^{n-1}a(l)(g_n^{ i })^{ (l-j)k } )
n1k=0∑n−1F(k)(gni)−jk=n1k=0∑n−1(l=0∑n−1a(l)(gni)lk)(gni)−jk=n1k=0∑n−1(l=0∑n−1a(l)(gni)(l−j)k)
式子
=
1
n
∑
k
=
0
n
−
1
(
a
(
0
)
(
g
n
i
)
(
0
−
j
)
k
+
a
(
1
)
(
g
n
i
)
(
1
−
j
)
k
+
⋯
+
a
(
n
−
1
)
(
g
n
i
)
(
(
n
−
1
)
−
j
)
k
)
=\frac{1}{n} \sum_{k=0}^{n-1}( a(0)(g_n^{ i })^{ (0-j)k} + a(1)(g_n^{ i })^{ (1-j)k} + \cdots + a(n-1)(g_n^{ i })^{ ((n - 1)-j)k} )
=n1k=0∑n−1(a(0)(gni)(0−j)k+a(1)(gni)(1−j)k+⋯+a(n−1)(gni)((n−1)−j)k)
展开
k
k
k的求和式得
=
1
n
[
a
(
0
)
(
(
g
n
i
)
(
0
−
j
)
0
+
(
g
n
i
)
(
0
−
j
)
1
+
⋯
+
(
g
n
i
)
(
0
−
j
)
(
n
−
1
)
)
+
a
(
1
)
(
(
g
n
i
)
(
1
−
j
)
0
+
(
g
n
i
)
(
1
−
j
)
1
+
⋯
+
(
g
n
i
)
(
1
−
j
)
(
n
−
1
)
)
+
⋯
+
a
(
n
−
1
)
(
(
g
n
i
)
(
(
n
−
1
)
−
j
)
0
+
(
g
n
i
)
(
(
n
−
1
)
−
j
)
1
+
⋯
+
(
g
n
i
)
(
(
n
−
1
)
−
j
)
(
n
−
1
)
)
]
=\frac{1}{n}[ a(0)((g_n^{ i })^{ (0-j)0 }+(g_n^{ i })^{ (0-j)1 } + \cdots + (g_n^{ i })^{ (0-j)(n-1) }) + \\ a(1)((g_n^{ i })^{ (1-j)0 }+(g_n^{ i })^{ (1-j)1 } + \cdots + (g_n^{ i })^{ (1-j)(n-1) }) + \\ \cdots + \\ a(n-1)((g_n^{ i })^{ ((n-1)-j)0 }+(g_n^{ i })^{ ((n-1)-j)1 } + \cdots + (g_n^{ i })^{ ((n-1)-j)(n-1) }) ]
=n1[a(0)((gni)(0−j)0+(gni)(0−j)1+⋯+(gni)(0−j)(n−1))+a(1)((gni)(1−j)0+(gni)(1−j)1+⋯+(gni)(1−j)(n−1))+⋯+a(n−1)((gni)((n−1)−j)0+(gni)((n−1)−j)1+⋯+(gni)((n−1)−j)(n−1))]
由等比数列的求和公式得:
a
(
x
)
(
(
g
n
i
)
(
x
−
j
)
0
+
(
g
n
i
)
(
x
−
j
)
1
+
⋯
+
(
g
n
i
)
(
x
−
j
)
(
n
−
1
)
)
=
a
(
x
)
1
−
(
(
g
n
i
)
n
(
x
−
j
)
)
n
1
−
(
g
n
i
)
(
x
−
j
)
a(x)((g_n^{ i })^{ (x-j)0 }+(g_n^{ i })^{ (x-j)1 } + \cdots + (g_n^{ i })^{ (x-j)(n-1) })=a(x) \frac{1- ((g_n^{ i })^{{n}(x-j)})^n}{1- (g_n^{ i })^{(x-j)} }
a(x)((gni)(x−j)0+(gni)(x−j)1+⋯+(gni)(x−j)(n−1))=a(x)1−(gni)(x−j)1−((gni)n(x−j))n当
j
≠
x
j \neq x
j=x,可得:
a
(
x
)
1
−
(
(
g
n
i
)
(
x
−
j
)
)
n
1
−
(
g
n
i
)
(
x
−
j
)
=
a
(
x
)
1
−
(
g
i
)
(
x
−
j
)
1
−
(
g
n
i
)
(
x
−
j
)
=
a
(
x
)
1
−
1
(
x
−
j
)
1
−
(
g
n
i
)
(
x
−
j
)
=
0
a(x) \frac{1- ((g_n^{ i })^{(x-j)})^n}{1- (g_n^{ i })^{(x-j)} }=a(x) \frac{1- (g^{ i })^{(x-j)} }{1- (g_n^{ i })^{(x-j)} }=a(x) \frac{1- 1^{ (x-j)}}{1- (g_n^{ i })^{(x-j)} }=0
a(x)1−(gni)(x−j)1−((gni)(x−j))n=a(x)1−(gni)(x−j)1−(gi)(x−j)=a(x)1−(gni)(x−j)1−1(x−j)=0
当
j
=
x
j = x
j=x,可得:
a
(
x
)
(
(
g
n
i
)
(
x
−
j
)
0
+
(
g
n
i
)
(
x
−
j
)
1
+
⋯
+
(
g
n
i
)
(
x
−
j
)
(
n
−
1
)
)
=
n
a
(
x
)
a(x)((g_n^{ i })^{ (x-j)0 }+(g_n^{ i })^{ (x-j)1 } + \cdots + (g_n^{ i })^{ (x-j)(n-1) })=na(x)
a(x)((gni)(x−j)0+(gni)(x−j)1+⋯+(gni)(x−j)(n−1))=na(x)
因而:
1
n
∑
k
=
0
n
−
1
F
(
k
)
(
g
n
i
)
−
j
k
=
1
n
∑
k
=
0
n
−
1
(
∑
l
=
0
n
−
1
a
(
l
)
(
g
n
i
)
l
k
)
(
g
n
i
)
−
j
k
=
n
a
(
x
)
\frac{1}{n} \sum_{k=0}^{n-1}F(k) (g_n^{ i })^{ -jk }=\frac{1}{n} \sum_{k=0}^{n-1}( \sum_{l=0}^{n-1}a(l)(g_n^{ i })^{ lk } )(g_n^{ i })^{- jk }=na(x)
n1k=0∑n−1F(k)(gni)−jk=n1k=0∑n−1(l=0∑n−1a(l)(gni)lk)(gni)−jk=na(x)
该矩阵的逆矩阵得证。
模板题
贴上DFT与IDFT结合起来的代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define ll long long
using namespace std;
const int N = 1 << 22;
const int g = 3 , gi = 332748118 , mod = 998244353;//不加const时间翻四倍
ll qw( ll a , ll b ) {
ll ans = 1;
while ( b ) {
if( b & 1 ) {
ans = ans * a % mod;
}
a = a * a % mod;
b >>= 1;
}
return ans;
}
int rev[N];
int n , m , l;
ll a[N] , b[N];
void pre( int bit ) {
for ( int i = 0 ; i < ( 1 << bit ) ; ++i ) {
rev[i] = (rev[i>>1]>>1)|((i&1)<<(bit - 1));
}
}
void NTT( ll *F , int len , int on ) {
for ( int i = 0 ; i < len ; ++i ) {//把递归的底层交换好
if ( i < rev[i] ) {
swap( F[i] , F[rev[i]] );
}
}
for ( int i = 2 ; i <= len ; i <<= 1 ) {//枚举步长,从递归的下面往上走
ll gn = qw( on ? g : gi , ( mod - 1 ) / ( i ) );
for ( int j = 0 ; j <= len - 1 ; j += i ) {//走一遍步长
ll gg = 1;
for ( int k = j ; k < j + i / 2 ; ++k ) {//枚举每块区间内的每一个元素
ll u = F[k];
ll t = gg * F[k + i / 2] % mod;
F[k] = (u + t) % mod;
F[k + i / 2] = ( u - t + mod ) % mod;
gg = gg * gn % mod;
}
}
}
return;
}
int main () {
int len = 0;
scanf("%d%d",&n,&m);
for ( int i = 0 ; i <= n ; ++i ) {
scanf("%lld",a + i);
}
for ( int i = 0 ; i <= m ; ++i ) {
scanf("%lld",b + i);
}
len = n + m;
int tim = 1;
l = 0;
while( tim <= len ) {
tim <<= 1;
l++;
}
len = tim;
pre(l);
NTT( a , len , 1 );
NTT( b , len , 1 );
for ( int i = 0 ; i <= len - 1 ; ++i ) {
a[i] = a[i] * b[i] % mod;
}
NTT( a , len , 0 );
ll inv = qw( len , mod - 2 );
for ( int i = 0 ; i <= n + m ; ++i ) {
printf("%lld ",a[i] * inv % mod);
}
return 0;
}
任意模数的坑以后再填吧QWQ。