NTT

NTT优缺点:

能取模

没有精度差

一般来说常数小
NTT常数小,通常比一大堆浮点运算的FFT要快(通常嘛)

讲一下任意模数NTT

众所周知,为了满足单位根的性质, N T T NTT NTT需要质数模数,而且需要质数模数能写成
p = a ∗ 2 k + 1 p=a*2^k+1 p=a2k+1的形式

比较常用的有998244353,1004535809,469762049,这三个原根都是3

n n n次多项式在模 m m m下乘积,最终系数一定不会大于 n m 2 nm^2 nm2找三个模数分别做 N T T NTT NTT再使用 C R T CRT CRT合并一下就好了,大概范围是 1 0 26 10^{26} 1026

//P4245
#define N 400005
int len, len1, len2, mod, lim = 1, L = 0;
int rev[N], F[N], G[N], A[N][3], B[N][3], ans[N];
int pr[3] = {469762049, 998244353, 1004535809};
int inv(int a, int b) {return fpow(a, b - 2, b);}
void NTTA(int u, int lim, int type) {
    int p = pr[u];
    int inv3 = fpow(3, p - 2, p);
    for (int i = 0 ; i < lim ; i++ )
        if (i < rev[i]) swap(A[i][u], A[rev[i]][u]);
    for (int i = 1 ; i < lim ; i <<= 1) {
        int wn = fpow(type == 1 ? 3 : inv3, (p - 1) / (i << 1), p);
        for (int j = 0 ; j < lim ; j += i << 1) {
            LL w = 1;
            for (int k = 0 ; k < i ; k ++, w = (w * wn) % p) {
                int x = A[j + k][u], y = 1ll * w * A[j + k + i][u] % p;
                A[j + k][u] = (1ll * x + y + p) % p;
                A[j + k + i][u] = (1ll * x - y + p) % p;
            }
        }
    }
    if (type == -1) {
        int inv = fpow(lim, p - 2, p);
        for (int i = 0; i < lim ; i ++) A[i][u] = 1ll * A[i][u] * inv % p;
    }
}
void NTTB(int u, int lim, int type) {
    int p = pr[u];
    int inv3 = fpow(3, p - 2, p);
    for (int i = 0 ; i < lim ; i++ )
        if (i < rev[i]) swap(B[i][u], B[rev[i]][u]);
    for (int i = 1 ; i < lim ; i <<= 1) {
        int wn = fpow(type == 1 ? 3 : inv3, (p - 1) / (i << 1), p);
        for (int j = 0 ; j < lim ; j += i << 1) {
            LL w = 1;
            for (int k = 0 ; k < i ; k ++, w = (w * wn) % p) {
                int x = B[j + k][u], y = 1ll * w * B[j + k + i][u] % p;
                B[j + k][u] = (1ll * x + y + p) % p;
                B[j + k + i][u] = (1ll * x - y + p) % p;
            }
        }
    }
    if (type == -1) {
        int inv = fpow(lim, p - 2, p);
        for (int i = 0; i < lim ; i ++) B[i][u] = 1ll * B[i][u] * inv % p;
    }
}
void CRT(){
    len = len1 + len2;
    LL a, b, c, t, k, M = 1ll * pr[0] * pr[1];
    LL inv1 = inv(pr[1], pr[0]), inv0 = inv(pr[0], pr[1]),inv3 = inv(M % pr[2], pr[2]);
    for (int i = 0; i <= len ; i++ ) {
        a = A[i][0], b = A[i][1], c = A[i][2];
        t = (mul(a * pr[1] % M, inv1, M) + mul(b * pr[0] % M, inv0, M)) % M;
        k = ((c - t % pr[2]) % pr[2] + pr[2]) % pr[2] * inv3 % pr[2];
        ans[i] = ((k % mod) * (M % mod) % mod + t % mod) % mod;
    }
}
int main() {
    len1 = rdi(), len2 = rdi(), mod = rdi();
    while (lim <= (len1 + len2)) lim <<= 1, L ++;
    for (int i = 0 ; i < lim ; i++ )rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (L - 1));
    for (int i = 0 ; i <= len1 ; i++ ) F[i] = rdi(), A[i][2] = A[i][1] = A[i][0] = F[i];
    for (int i = 0 ; i <= len2 ; i++ ) G[i] = rdi(), B[i][2] = B[i][1] = B[i][0] = G[i];
    for (int u = 0 ; u <= 2 ; u++ ) { 
        NTTA(u, lim, 1), NTTB(u, lim, 1);
        for (int i = 0 ; i < lim ; i++ ) A[i][u] = 1ll * A[i][u] * B[i][u] % pr[u];
        NTTA(u, lim, -1);
    }
    CRT();
    for (int i = 0 ; i <= len ; i++ ) write_(ans[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值