NTT学习笔记
阶
若((a,p)=1,p >1)
那么对于满足 (a^requiv 1 pmod p)最小的(r),称为(a)模(p)的阶,记作(delta_p(a))
原根
对于正整数(p),若有整数(a),满足(delta_p(a)=varphi(p)),称(a)为(p)的一个原根。
-
原根(a^0,a^1,dots,a^{varphi(p)-1})遍历(mod p)的剩余系
-
若(p)有原根,那么它一定有(varphi(varphi(p)))个原根
-
原根的分布,(2,4,p^a,2p^a,dots),其中(p)为素数且(age 1)
有限制的NTT
考虑使用原根替代单位根,则需要满足以下一些性质
- (w_n^k)互异
- ((w_{2n}^k)^2=w_n^k),折半引理
- (w_n^a*w_n^b=w_n^{ab})
- (w_n^{frac{n}{2}}=-1)
- (sumlimits_{i=0}^{n-1} w_n^i=0),进行逆变换
于是我们用原根这样定义,(w_n^k=(g^{frac{p-1}{n}})^k)
其中(g)是质数(p)的原根,质数(p)被表示成(p=an+1)的形式,(n)为(2)的正整数幂。
利用原根的性质和代入计算式,可以验证上面的性质是正确的。
然后其他的和(FFT)一样就可以了。
(p)一般取(998244353,1004535809,469762049),这三个数的原根都是(3)
#include <cstdio>
#include <algorithm>
#define ll long long
const int N=(1<<21)+10;
const ll mod=998244353,G=3,Gi=332748118;
ll a[N],b[N];int n,m,turn[N],len=1,L=-1;
ll quickpow(ll d,ll k)
{
ll f=1;
while(k)
{
if(k&1) f=f*d%mod;
d=d*d%mod;
k>>=1;
}
return f;
}
void NTT(ll *a,int typ)
{
for(int i=0;i<len;i++)
if(i<turn[i])
std::swap(a[i],a[turn[i]]);
for(int le=1;le<len;le<<=1)
{
ll wn=quickpow(typ?G:Gi,(mod-1)/(le<<1));
for(int p=0;p<len;p+=le<<1)
{
ll w=1;
for(int i=p;i<p+le;i++,w=w*wn%mod)
{
ll tmpx=a[i],tmpy=w*a[i+le]%mod;
a[i]=(tmpx+tmpy)%mod;
a[i+le]=(tmpx-tmpy)%mod;
}
}
}
}
int main()
{
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);
while(len<=n+m) len<<=1,++L;
for(int i=0;i<len;i++) turn[i]=turn[i>>1]>>1|(i&1)<<L;
NTT(a,1),NTT(b,1);
for(int i=0;i<len;i++) a[i]=a[i]*b[i]%mod;
NTT(a,0);
ll inv=quickpow(len,mod-2);
for(int i=0;i<=n+m;i++) printf("%lld ",(a[i]*inv%mod+mod)%mod);
return 0;
}
任意模数NTT
看了一下,暂时感觉意义不大,先咕咕掉