任意模数ntt_【模板篇】NTT和三模数NTT

本文介绍了如何在模运算环境下寻找类似离散傅立叶变换(DFT)的快速数论变换(NTT),重点讨论了原根的概念及其在NTT中的应用。通过预处理原根的幂和逆元,实现模数下的快速数论变换。文章还探讨了如何应对任意模数的NTT问题,提出使用三模数NTT的方法,结合中国剩余定理来解决大范围数据的模运算。此外,提供了相关代码示例以辅助理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

之前写过FFT的笔记. 我们知道FFT是在复数域上进行的变换.

而且经过数学家的证明, DFT是复数域上唯一满足循环卷积性质的变换.

而我们在OI中, 经常遇到对xxxx取模的题目, 这就启发我们可不可以在模运算的意义下找一个这样的变换.

然后我们发现有个神奇的东西, 原根\(g\), 这东西在模意义下相当于单位复根\(-e^{\frac{2\pi i}{n}}\).

所以我们预处理一下\(g\)的幂和逆元, 然后改一下fft的代码就出现了快速数论变换ntt

懒得写了 直接上代码:

void getwn(){ //预处理原根的幂和逆元

int x=qpow(3,p-2);

for(int i=0;i<20;++i){

wn[i]=qpow(3,(p-1)/(1<

inv[i]=qpow(x,(p-1)/(1<

}

}

void ntt(int *y,bool f){ rev(y); //翻转代码和fft无异

for(int m=2,id=1;m<=n;m<<=1,++id){ //id用来记录转到第几下了

for(int k=0;k

int w=1,wm=f?wn[id]:inv[id]; //如果是dft就用幂, idft就用幂的逆元

for(int j=0;j

//这里跟fft一样, 不过要对p取模

int u=y[k+j]%p,t=1ll*w*y[k+j+m/2]%p;

y[k+j]=u+t; if(y[k+j]>p) y[k+j]-=p;

y[k+j+m/2]=u-t; if(y[k+j+m/2]<0) y[k+j+m/2]+=p;

w=1ll*w*wm%p;

}

}

}

if(!f){

int x=qpow(n,p-2);

for(int i=0;i

y[i]=1ll*y[i]*x%p;

}

}

好像差不多呢~ 不过这样就要求我们找一个原根好求的数. 比如著名的uoj数: 998244353 还有1004535809和469762049等, 这三个数原根都是3~

好像因为当时一看到模数不是1e9+7一般就会想到ntt, vfk为了防止这一点, 模数统一采用998244353, 现在看看收效不错.

不过 有些丧心病狂的人就是要用1e9+7作为ntt的模数, 甚至还出现了可以不模质数的情况!

那我们怎么解决任意模数ntt呢? 我们可以采用拆系数ntt或者三模数ntt. 这里介绍一下三模数ntt.

对于一般的数据范围, \(n\leq10^5, a_i\leq10^9\), 这样可能会到10^5*10^{9^2}=10^{23}级别.

所以我们可以找三个乘积\(>10^{23}\)的ntt-friendly的数, 然后分别ntt再想办法合并.

我们假如答案是ans, 那我们做三次ntt后就能得到如下三个柿子.

\[

\left\{\begin{matrix}

ans\equiv a_1(\mod m_1)\\

ans\equiv a_2(\mod m_2)\\

ans\equiv a_3(\mod m_3)

\end{matrix}\right.

\]

我们把前两个柿子通过中国剩余定理合并, 就可以得到

\[

\left\{\begin{matrix}

ans\equiv A(\mod M)\\

ans\equiv a_3(\mod m_3)

\end{matrix}\right.

\]

其中, \(M=m_1*m_2\)

这样我们设\(ans=kM+A\),

\[

kM+A\equiv a_3(\mod m_3) \k=(a_3-A)*M^{-1} (\mod m_3)

\]

这样我们求出\(k\)然后代回到\(ans=kM+A\)就可以求对任意模数取模的结果了.

中国剩余定理合并的时候直接乘是可以爆long long的, 所以我们要用到\(O(1)\)快速乘~

下面上一波代码: luogu4245 【模板】MTT

哎呀觉得自己码风有点丑啊qwq

#include

#include

#include

typedef long long LL;

const int N=600020,p0=469762049,p1=998244353,p2=1004535809;

const LL M=1ll*p0*p1;

int wn[20],nw[20],rev[N],n,lg,p;

int qpow(int a,int b,int p,int s=1){

for(;b;b>>=1,a=1ll*a*a%p)

if(b&1) s=1ll*s*a%p;

return s;

}

LL mul(LL a,LL b,LL p){ a%=p; b%=p;

return (a*b-(LL)((long double)a*b/p)*p+p)%p;

}

void calcw(int p){

int x=qpow(3,p-2,p);

for(int i=0;i<20;++i){

wn[i]=qpow(3,(p-1)/(1<

nw[i]=qpow(x,(p-1)/(1<

}

}

void init(){

for(int i=0;i

rev[i]=(rev[i>>1]>>1)|((i&1)<

}

void ntt(int *y,bool f,int p){ calcw(p);

for(int i=0;i

for(int m=2,id=1;m<=n;m<<=1,++id){

for(int k=0;k

int w=1,wm=f?wn[id]:nw[id];

for(int j=0;j>1;++j){

int &a=y[k+j]; int &b=y[k+j+m/2];

int u=a%p,t=1ll*w*b%p;

a=u+t; if(a>p) a-=p;

b=u-t; if(b<0) b+=p;

w=1ll*w*wm%p;

}

}

} int x=qpow(n,p-2,p);

if(!f) for(int i=0;i

}

char c1[N],c2[N]; int a[N],b[N],c[N],d[N],ans[3][N];

int main(){

int l1,l2; scanf("%d%d%d",&l1,&l2,&p);

for(int i=0;i<=l1;++i) scanf("%d",&a[i]),a[i]%=p;

for(int i=0;i<=l2;++i) scanf("%d",&b[i]),b[i]%=p;

for(n=1;n

std::copy(a,a+n,c); std::copy(b,b+n,d);

ntt(c,1,p0); ntt(d,1,p0);

for(int i=0;i

std::copy(a,a+n,c); std::copy(b,b+n,d);

ntt(c,1,p1); ntt(d,1,p1);

for(int i=0;i

std::copy(a,a+n,c); std::copy(b,b+n,d);

ntt(c,1,p2); ntt(d,1,p2);

for(int i=0;i

ntt(ans[0],0,p0); ntt(ans[1],0,p1); ntt(ans[2],0,p2);

for(int i=0;i

LL A=mul(1ll*ans[0][i]*p1%M,qpow(p1%p0,p0-2,p0),M)

+mul(1ll*ans[1][i]*p0%M,qpow(p0%p1,p1-2,p1),M);

if(A>M) A-=M;

LL k=((ans[2][i]-A)%p2+p2)%p2*qpow(M%p2,p2-2,p2)%p2;

a[i]=1ll*(k%p)*(M%p)%p+A%p;

if(a[i]>p) a[i]-=p;

}

for(int i=0;i<=l1+l2;++i) printf("%d",a[i]);

}

原文:https://www.cnblogs.com/enzymii/p/8925992.html

<think>嗯,用户的问题是关于在C语言中实现NTT算法,模数3365569,处理1728项的数论变换。首先,我需要确认NTT的基本原理以及如何适应这个特定的模数项数。 首先,数论变换(NTT)是快速傅里叶变换(FFT)在有限域上的类比,用于多项式乘法的高效计算。实现NTT需要满足模数存在足够的本原根,并且项数是2的幂次或者能被分解为适合的因子。用户提到的模数3365569,我需要验证它是否满足NTT的条件。 接下来,检查模数3365569是否为素数。假设3365569是一个素数,那么需要找到它的本原根。同时,模数减1后的值是否能被1728整除,因为NTT的长度需要是2的幂次或者满足某种分解。例如,如果模数p=3365569,那么p-1应该包含足够多的因子来支持NTT的长度。例如,1728=12^3=2^6*3^3,所以需要确认p-1是否包含这些因子。 假设p=3365569,计算p-1=3365568。分解3365568的因数:我需要先进行因数分解。3365568 ÷ 1728=1944,所以可能3365568=1728*1944,但需要更详细的分解。可能分解为2^6 *3^3 *其他因子,如果这样的话,可能支持1728点的NTT。 然后,需要找到适当的本原根旋转因子。例如,选择一个本原根g,使得g^( (p-1)/n )是n次单位根,其中n=1728。这里可能需要计算g的值,并预处理旋转因子数组。 接下来,考虑C语言实现。需要处理大整数的模运算,模数3365569可能较大,需要确保使用适当的数据类型,如uint32_t或uint64_t,并优化模乘操作,可能使用Barrett约减或Montgomery乘法来提高效率。 另外,用户提到参考了FFT实现多项式乘法的例子,所以可能需要类似的结构,包括预处理数组、迭代或递归的NTT实现、以及最终的逆变换。逆变换需要使用模逆元,即模数下的倒数,因此需要计算n的逆元旋转因子的逆。 还需要注意内存管理,特别是处理1728项数组时,可能需要动态分配内存或使用静态数组,但要确保栈空间足够。 可能的问题点包括:模数的选择是否适合NTT,本原根是否存在,旋转因子的计算是否正确,以及模运算的效率是否足够。需要验证这些步骤的正确性,可能参考已有的NTT实现或数学工具来确认参数。 最后,用户可能需要具体的代码示例,因此需要提供一个基本的框架,包括预计算旋转因子、NTT函数、逆NTT函数,以及模运算的辅助函数。同时,需要提醒用户进行测试优化,特别是在大模数下的性能问题。</think>### C语言实现NTT算法(模数3365569,1728项) #### 1. 验证模数适用性 数论变换要求模数$p$满足$p = k \times 2^n + 1$(通常为素数),且$p-1$需包含足够因子支持变换长度。此处: - $p = 3365569$ - $p-1 = 3365568 = 1728 \times 1944 = 2^6 \times 3^3 \times 7 \times 13$ 该模数支持最大变换长度1728,满足题目需求[^1]。 #### 2. 寻找本原根旋转因子 1. **本原根选择**: 通过暴力搜索找到最小本原根$g=5$,验证$g^{(p-1)/d} \not\equiv 1 \ (\text{mod} \ p)$对所有$d|(p-1)$成立。 2. **旋转因子计算**: 生成旋转因子$W_n = g^{(p-1)/n} \ \text{mod} \ p$,其中$n=1728$: $$W_{1728} = 5^{3365568/1728} \ \text{mod} \ 3365569 = 1908612$$ #### 3. 核心算法实现 ```c #include <stdint.h> #define MOD 3365569 #define N 1728 #define W 1908612 // W_N = g^((p-1)/N) mod p // 快速幂取模 uint32_t pow_mod(uint32_t a, uint32_t b) { uint32_t res = 1; while (b) { if (b & 1) res = (uint64_t)res * a % MOD; a = (uint64_t)a * a % MOD; b >>= 1; } return res; } // 预处理旋转因子 void precompute_twiddles(uint32_t *twiddles, int n, int w) { twiddles[0] = 1; for (int i = 1; i < n; ++i) { twiddles[i] = (uint64_t)twiddles[i-1] * w % MOD; } } // 蝶形运算优化 void ntt(uint32_t *a, int logn, const uint32_t *twiddles) { int n = 1 << logn; for (int i = 0; i < n; ++i) { int rev = 0; for (int j = 0; j < logn; ++j) rev |= ((i >> j) & 1) << (logn - j - 1); if (i < rev) { uint32_t tmp = a[i]; a[i] = a[rev]; a[rev] = tmp; } } for (int s = 2; s <= n; s <<= 1) { int m = s >> 1; for (int j = 0; j < n; j += s) { for (int k = 0; k < m; ++k) { uint32_t t = (uint64_t)twiddles[k * (n/s)] * a[j + k + m] % MOD; a[j + k + m] = (a[j + k] + MOD - t) % MOD; a[j + k] = (a[j + k] + t) % MOD; } } } } // 逆NTT需使用W^{-1} mod p uint32_t invW = pow_mod(W, MOD-2); // 费马小定理求逆 ``` #### 4. 关键优化技术 1. **Montgomery约减**:针对模运算优化 ```c #define MONTGOMERY_R 0x9F0B3240 // R=2^32 mod 3365569 uint32_t mont_reduce(uint64_t x) { uint32_t q = x * 3365569; uint64_t t = (x - (uint64_t)q * MOD) >> 32; return t >= MOD ? t - MOD : t; } ``` 2. **AVX2指令集**:使用SIMD并行化蝶形运算(参考CHES 2022优化方法[^2]) #### 5. 验证与测试 ```c int main() { uint32_t a[N] = {/* 初始化数据 */}; uint32_t twiddles[N]; precompute_twiddles(twiddles, N, W); ntt(a, log2(N), twiddles); // 逆变换实现类似,需使用invW return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值