1.剩余系: 所有整数模正整数n得到的结果组成的集合称为n的剩余系,n的剩余系即小于n的非负整数,记为 Z n Z_n Zn
2.简化剩余系: 在n的剩余系中与n互质的元素的集合,称为n的简化剩余系,记为 Z n ∗ Z_n^* Zn∗
3.欧拉函数: n的简化剩余系中元素的个数,称为欧拉函数,记为 ϕ ( n ) \phi(n) ϕ(n)
3.原根: 对于互质的两个正整数g和n,如果g模n的阶为 ϕ ( n ) \phi(n) ϕ(n),则称x为n的原根.换句话说,即对于 1 ≤ j < ϕ ( n ) 1 \leq j < \phi(n) 1≤j<ϕ(n),使得 g j ≠ 1 ( m o d n ) g^{j} \neq 1 (mod \ n) gj̸=1(mod n),但 g ϕ ( n ) = 1 ( m o d n ) g^{\phi(n)}=1 (mod \ n) gϕ(n)=1(mod n)则称g为n的原根.
如何求原根?
求n的原根,似乎只能暴力枚举 g ( 2 ≤ g ≤ n − 1 ) g (2\leq g \leq n-1) g(2≤g≤n−1),然后检测 g p m o d n g^p mod\ n gpmod n是否等于1,此处p为 ϕ ( n ) x \frac{\phi(n)}{x} xϕ(n),x为 ϕ ( n ) \phi(n) ϕ(n)的质因数.如果存在一个p使得 g p m o d n g^p mod \ n gpmod n等于1,则g不是原根,否则g为原根.
一般原根都很小,所以暴力枚举即可.
在FFT中,我们选择n次单位复根作为x的值,因为它们符合消去引理、折半引理和求和引理,于是可以采用分治的方法将时间复杂度将为 O ( N l o g N ) O(NlogN) O(NlogN).但是因为使用了复数,所以在精度上可能会有些误差.而且有的题目,多项式相乘是带模的乘法,此时我们不能使用FFT了.那能不能取整数值来作为x的值呢?其实,只要该值也像主n次单位复根那样符合消去引理、折半引理和求和引理呢,则他们也是采用分治来加速的.
在数论当中,我们发现某些数的简化剩余系在乘法运算下构成的群与n次单位复根在乘法运算的群有相似的性质.如果 m m m存在原根 x x x,则 m m m的简化剩余系 Z m ∗ = { x j ( m o d m ) ∣ 1 ≤ j ≤ ϕ ( n ) } Z_m^*=\{x^j (mod \ m) |1\leq j \leq \phi(n)\} Zm∗={xj(mod m)∣1≤j≤ϕ(n)}.
设 ϕ ( m ) = k ∗ 2 n \phi(m)=k*2^n ϕ(m)=k∗2n, x i = x ϕ ( m ) / i ( m o d m ) x_i=x^{\phi(m)/i} (mod \ m) xi=xϕ(m)/i(mod m) , i i i也为2的幂,且 i ≤ 2 n i\leq 2^n i≤2n
则易知 x i x_i xi满足:
1.消去引理
x i d j d = x i j ( m o d m ) x_{id}^{jd}=x_i^{j} (mod \ m) xidjd=xij(mod m)
根据定义即可证明.
2.折半引理:
( x i j + i / 2 ) 2 = x i 2 j + i = ( x i j ) 2 ∗ x i i = ( x i j ) 2 ( m o d m ) (x_{i}^{j+i/2})^2=x_i^{2j+i}=(x_i^j)^2*x_i^i=(x_i^j)^2 (mod \ m) (xij+i/2)2=xi2j+i=(xij)2∗xii=(xij)2(mod m)
根据消去引理可以证明.
3.求和引理:
当m>2且k不是i的倍数时,下式成立:
∑ j = 0 i − 1 ( x i k ) j = 0 ( m o d m ) \sum_{j=0}^{i-1}(x_i^k)^j=0 (mod \ m) j=0∑i−1(xik)j=0(mod m)
根据等比数列求和公式可以证明.
所以,我们可以取m的原根,像FFT一样,在 O ( N l o g N ) O(NlogN) O(NlogN)的时间内完成多项式乘法.
使用NTT时有些限制,如果多项式乘法不要求取模,则我们要找足够大的质数m,并且 m − 1 = k ∗ 2 n m-1=k*2^n m−1=k∗2n,要保证n大于多项式的项数,m还要大于多项式的系数.
如果多项式乘法是带模乘法,则只能用NTT,不能使用FFT.此时,若m为质数,则要求 m − 1 = k ∗ 2 n m-1=k*2^n m−1=k∗2n,n要大于多项式的系数.若m不为质数,则需要用中国剩余定理来做.
三.模板
使用NTT计算多项式的乘积
#include
#include
#include
#include
#include
using namespace std;
#define LL long long int
#define MAXN 400005
LL num1[MAXN],num2[MAXN];
int n,m,len;
const LL G=3;
const LL MOD=998244353;
LL w,wn;
LL ksm(LL x,LL k){
LL res=1;
while(k)
{
if(k&1)
res=res*x%MOD;
x=x*x%MOD;
k>>=1;
}
return res;}
void change(LL *num,int len){
for(int i=1,j=len/2;i=k)
{
j-=k;
k/=2;
}
if(j