前置知识
### 介绍 一般的NTT的模数$P$都要满足$P=r\times 2^k+1$。但如果不满足这个条件,那就不能直接用NTT了,需要用到任意模数NTT。
下面就来介绍一种任意模数NTT——三模数NTT。
三模数NTT
三模数NTT,即找三个模数,这三个模数满足上面NTT的三个条件,分别求出多项式乘法在这三个模数下的结果,然后利用中国剩余定理,求出在给定模数下的结果。
设给定模数为 p p p,自己找的三个模数为 m o d 1 , m o d 2 , m o d 3 mod1,mod2,mod3 mod1,mod2,mod3,则这三个模数还要满足
- m o d 1 × m o d 2 × m o d 3 mod1\times mod2\times mod3 mod1×mod2×mod3大于结果多项式中的所有系数
- m o d 1 × m o d 2 mod1\times mod2 mod1×mod2在long long范围内
对于结果多项式的系数 c c c,有:
f ( i ) = { c ≡ c 1 ( m o d m o d 1 ) c ≡ c 2 ( m o d m o d 2 ) c ≡ c 3 ( m o d m o d 3 ) f(i)= \left\{\begin{matrix} c\equiv c_1\pmod{mod1}\\ c\equiv c_2\pmod{mod2}\\ c\equiv c_3\pmod{mod3} \end{matrix}\right. f(i)=⎩ ⎨ ⎧c≡c1(modmod1)c≡c2(modmod2)c≡c3(modmod3)
根据扩展中国剩余定理,由前两个方程可得
c
1
+
m
o
d
1
×
k
1
=
c
2
+
m
o
d
2
×
k
2
c_1+mod1\times k_1=c_2+mod2\times k_2
c1+mod1×k1=c2+mod2×k2
k
1
×
m
o
d
1
≡
(
c
2
−
c
1
)
(
m
o
d
m
o
d
2
)
k_1\times mod1\equiv (c_2-c_1)\pmod{mod2}
k1×mod1≡(c2−c1)(modmod2)
k
1
≡
(
c
2
−
c
1
)
×
m
o
d
1
−
1
(
m
o
d
m
o
d
2
)
k_1\equiv (c_2-c_1)\times mod1^{-1}\pmod{mod2}
k1≡(c2−c1)×mod1−1(modmod2)
因为
c
=
c
1
+
m
o
d
1
×
k
1
c=c_1+mod1\times k_1
c=c1+mod1×k1,所以
c
≡
(
c
2
−
c
1
)
×
m
o
d
1
−
1
×
m
o
d
1
+
c
1
(
m
o
d
m
o
d
1
×
m
o
d
2
)
c\equiv(c_2-c_1)\times mod1^{-1}\times mod1+c_1\pmod{mod1\times mod2}
c≡(c2−c1)×mod1−1×mod1+c1(modmod1×mod2)
在这个式子中, m o d 1 − 1 mod1^{-1} mod1−1是指 m o d 1 mod1 mod1在模 m o d 2 mod2 mod2的意义下的逆元,所以 m o d 1 − 1 × m o d 1 mod1^{-1}\times mod1 mod1−1×mod1不一定为 1 1 1。
设 m = ( c 2 − c 1 ) × m o d 1 − 1 × m o d 1 + c 1 m=(c_2-c_1)\times mod1^{-1}\times mod1+c_1 m=(c2−c1)×mod1−1×mod1+c1,则上面的式子变为 c ≡ m ( m o d m o d 1 × m o d 2 ) c\equiv m\pmod{mod1\times mod2} c≡m(modmod1×mod2),将这个式子和第三个式子联立,同样可以得到
c ≡ ( c 3 − m ) × ( m o d 1 × m o d 2 ) − 1 × ( m o d 1 × m o d 2 ) + m ( m o d m o d 1 × m o d 2 × m o d 3 ) c\equiv (c_3-m)\times(mod1\times mod2)^{-1}\times (mod1\times mod2)+m\pmod{mod1\times mod2\times mod3} c≡(c3−m)×(mod1×mod2)−1×(mod1×mod2)+m(modmod1×mod2×mod3)
同样地, ( m o d 1 × m o d 2 ) − 1 (mod1\times mod2)^{-1} (mod1×mod2)−1是在模 m o d 3 mod3 mod3的意义下的逆元。
要让 m o d 1 × m o d 2 mod1\times mod2 mod1×mod2不超过long long的范围,是防止溢出。而三个模数的乘积虽然会爆long long,但因为三个模数的乘积大于结果多项式中的所有系数,所以我们并不需要真的去模 m o d 1 × m o d 2 × m o d 3 mod1\times mod2\times mod3 mod1×mod2×mod3。
这三个模数可以选择 998244353 , 1004535809 , 469762049 998244353,1004535809,469762049 998244353,1004535809,469762049,这三个质数的原根都为 3 3 3。
整个过程中,总共有 9 9 9次 D F T DFT DFT或 I D F T IDFT IDFT。
时间复杂度为 O ( n log n ) O(n\log n) O(nlogn),常数有一点大。
例题
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1000000,g=3;
const ll mod1=998244353,mod2=1004535809,mod3=469762049;
int n,m,p,len=1;
ll w,wn,a[N+5],b[N+5],c1[N+5],c2[N+5],c3[N+5],x[N+5],y[N+5];
ll mi(ll t,ll v,ll mod){
if(!v) return 1;
ll re=mi(t,v/2,mod);
re=re*re%mod;
if(v&1) re=re*t%mod;
return re;
}
void ch(ll *c,int l){
for(int i=1,j=l/2,k;i<l-1;i++){
if(i<j) swap(c[i],c[j]);
k=l/2;
while(j>=k){
j-=k;k>>=1;
}
j+=k;
}
}
void ntt(ll *c,int l,int fl,ll mod){
ch(c,l);
for(int i=2;i<=l;i<<=1){
if(fl==1) wn=mi(g,(mod-1)/i,mod);
else wn=mi(g,mod-1-(mod-1)/i,mod);
for(int j=0;j<l;j+=i){
w=1;
for(int k=j;k<j+i/2;k++,w=w*wn%mod){
ll t=c[k],u=w*c[k+i/2]%mod;
c[k]=(t+u)%mod;
c[k+i/2]=(t-u+mod)%mod;
}
}
}
if(fl==-1){
ll ny=mi(l,mod-2,mod);
for(int i=0;i<l;i++) c[i]=c[i]*ny%mod;
}
}
void mtt(ll *c,ll mod){
for(int i=0;i<len;i++){
x[i]=a[i];y[i]=b[i];
}
ntt(x,len,1,mod);
ntt(y,len,1,mod);
for(int i=0;i<len;i++){
c[i]=x[i]*y[i]%mod;
}
ntt(c,len,-1,mod);
}
int main()
{
scanf("%d%d%d",&n,&m,&p);
for(int i=0;i<=n;i++) scanf("%lld",&a[i]);
for(int i=0;i<=m;i++) scanf("%lld",&b[i]);
while(len<n+m+1) len<<=1;
mtt(c1,mod1);
mtt(c2,mod2);
mtt(c3,mod3);
ll mod12=mod1*mod2,ny1=mi(mod1,mod2-2,mod2),ny12=mi(mod12%mod3,mod3-2,mod3);
for(int i=0;i<len;i++){
c1[i]=(((c2[i]-c1[i])%mod2+mod2)%mod2*ny1%mod2*mod1+c1[i])%mod12;
c3[i]=(((c3[i]-c1[i])%mod3+mod3)%mod3*ny12%mod3*(mod12%p)%p+c1[i]%p)%p;
}
for(int i=0;i<n+m+1;i++){
printf("%lld ",c3[i]);
}
return 0;
}