F F T FFT FFT有时候会被卡精度?所以可能会有模数,有了模数以后就需要模数的原根。
原根是什么?(留坑待填)
N
T
T
NTT
NTT有很多种解决方法
1.
1.
1.特殊模数
(
2
k
+
1
)
∣
(
p
−
1
)
,
(
p
−
1
)
>
D
F
T
的
长
度
(2k+1)|(p−1),(p−1)>DFT的长度
(2k+1)∣(p−1),(p−1)>DFT的长度,可以直接暴力求原根
g
g
g,用
g
g
g代替单位复数根
2. 2. 2.一般模数
- 三模数法(9次DFT)
如果这个模数的原根不好求,而模数又很大,可以用三个模数
要求 m o d 1 × m o d 2 × m o d 3 ≥ n × p 2 mod1\times mod2\times mod3\ge n\times p^2 mod1×mod2×mod3≥n×p2
常用 998244353 , 1004535809 , 469762049 998244353,1004535809,469762049 998244353,1004535809,469762049,因为他们的原根都是 3 3 3
对这几个模数分别做 D F T DFT DFT,然后用中国剩余定理合并,然后对原模数取模即可
中国剩余定理部分:
a n s ≡ c 1 m o d m 1 ans\equiv c1\ mod\ m1 ans≡c1 mod m1
a n s ≡ c 2 m o d m 2 ans\equiv c2\ mod\ m2 ans≡c2 mod m2
a n s ≡ c 3 m o d m 3 ans\equiv c3\ mod\ m3 ans≡c3 mod m3
如果直接合并的话会爆 l o n g l o n g long\ long long long,可以先合并两个,再用奇技淫巧合并第三个
合并前两个:
a n s ≡ ( c 1 × m 2 × i n v ( m 2 , m 1 ) + c 2 × m 1 × i n v ( m 1 , m 2 ) ) m o d ( m 1 × m 2 ) ans\equiv (c1\times m2\times inv(m2,m1)+c2\times m1\times inv(m1,m2)) mod\ (m1\times m2) ans≡(c1×m2×inv(m2,m1)+c2×m1×inv(m1,m2))mod (m1×m2)
其中 i n v ( x , y ) inv(x,y) inv(x,y)表示 x x x对 y y y取模的逆元。
把上式化简为 a n s ≡ C m o d M ans\equiv C\ mod\ M ans≡C mod M
设 a n s = x × M + C = y × m 3 + c 3 ans=x\times M+C=y\times m3+c3 ans=x×M+C=y×m3+c3
接下来很重要:求出 x m o d m 3 x\ mod\ m3 x mod m3意义下的值:
x ≡ ( c 3 − C ) × M − 1 m o d m 3 x\equiv (c3-C)\times M^{-1}\ mod\ m3 x≡(c3−C)×M−1 mod m3
这样就可以算出右半部分的值 q q q,令 x = k × m 3 + q x=k\times m3+q x=k×m3+q,代入 a n s ans ans得:
a n s = k × m 1 × m 2 × m 3 + q × M + C ans=k\times m1\times m2\times m3+q\times M+C ans=k×m1×m2×m3+q×M+C
因为 a n s ∈ [ 0 , m 1 × m 2 × m 3 ) ans\in [0,m1\times m2\times m3) ans∈[0,m1×m2×m3),所以 k = 0 k=0 k=0,于是 a n s ans ans就可直接计算。
做9次DFT,常数极大
有一道模板题luogu4245
直接用三模数法,代码如下:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define LL long long
#define maxn 400005
using namespace std;
const LL mod1=998244353,mod2=1004535809,mod3=469762049,g=3;
const LL M=1LL*mod1*mod2;
inline int rd(){
int x=0,f=1;char c=' ';
while(c<'0' || c>'9') f=c=='-'?-1:1,c=getchar();
while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
return x*f;
}
int n,m,p,rev[maxn],limit=1,l;
LL a[3][maxn],b[3][maxn],ans[maxn];
inline LL mul(LL x,int k,LL MOD){
LL ret=0;
while(k){
if(k&1) (ret+=x)%=MOD;
(x+=x)%=MOD; k>>=1;
} return ret%MOD;
}
inline LL qpow(LL x,int k,int MOD){
LL ret=1;
while(k){
if(k&1) ret=ret*x%MOD;
x=x*x%MOD; k>>=1;
} return ret%MOD;
}
inline void NTT(LL *F,int type,int MOD){
for(int i=0;i<limit;i++){
F[i]%=MOD;
if(i<rev[i]) swap(F[i],F[rev[i]]);
}
for(int mid=1;mid<limit;mid<<=1){
LL Wn=qpow(g,type==1?(MOD-1)/(mid<<1):(MOD-1-(MOD-1)/(mid<<1)),MOD);//!
for(int r=mid<<1,j=0;j<limit;j+=r){
LL w=1;
for(int k=0;k<mid;k++,w=w*Wn%MOD){
LL x=F[j+k],y=w*F[j+mid+k]%MOD;
F[j+k]=(x+y)%MOD; F[j+mid+k]=(x-y+MOD)%MOD;
}
}
}
if(type==-1){
LL INV=qpow(limit,MOD-2,MOD);//除以limit
for(int i=0;i<limit;i++) F[i]=F[i]*INV%MOD;
}
}
inline void CRT(){
for(int i=0;i<limit;i++){
LL tmp=0;
(tmp+=mul(a[0][i]*mod2%M,qpow(mod2,mod1-2,mod1),M))%=M;
(tmp+=mul(a[1][i]*mod1%M,qpow(mod1,mod2-2,mod2),M))%=M;
a[1][i]=tmp;
}
for(int i=0;i<limit;i++){//x=(c3-C)/M mod mod3
LL tmp=(a[2][i]-a[1][i]%mod3+mod3)%mod3*qpow(M%mod3,mod3-2,mod3)%mod3;
ans[i]=(M%p*tmp%p+a[1][i]%p)%p;
}
}
inline void solve(int x,int MOD){
NTT(a[x],1,MOD); NTT(b[x],1,MOD);
for(int i=0;i<limit;i++) a[x][i]=a[x][i]*b[x][i]%MOD;
NTT(a[x],-1,MOD);
}
int main(){
n=rd(); m=rd(); p=rd();
for(int i=0;i<=n;i++){
int x=rd();
for(int j=0;j<3;j++) a[j][i]=x%p;
}
for(int i=0;i<=m;i++){
int x=rd();
for(int j=0;j<3;j++) b[j][i]=x%p;
}
while(limit<=n+m) limit<<=1,++l;
for(int i=0;i<limit;i++)
rev[i]=(rev[i>>1]>>1)|((i&1)<<(l-1));
solve(0,mod1); solve(1,mod2); solve(2,mod3);
CRT();
for(int i=0;i<=n+m;i++) printf("%lld ",ans[i]);
return 0;
}
-
拆系数法(7次DFT)
将两个多项式系数拆开,然后发现可以分成三部分计算,不很常用而且精度掉的厉害,这里先不做详细解释 -
MTT(4次DFT)
奇技淫巧,话说三模数NTT卡卡常多预处理一些应该都能过去,不行就开O2,跑的蛮快的(反正省选也开O2),这个东西先放着,以后闲得没事再来看