- 概念引入

  - 阶
    对于$p \in N_+$且$(a, \ p) = 1$,满足$a^r \equiv 1 (mod \ p)$的最小的非负$r$为$a$模$p$意义下的阶,记作$\delta_p(a)$

  - 原根
    定义:若$p \in N_+$且$a \in N$,若$\delta_p(a) = \phi(p)$,则称$a$为模$p$的一个原根
    相关定理:
      - 若一个数$m$拥有原根,那么它必定为$2, \ 4, \ p^t, \ 2p^t \ (p$为奇质数$)$的其中一个
      - 每个数$p$都有$\phi(\phi(p))$个原根
      证明:若$p \in N_+$且$(a, \ p) = 1$,正整数$r$满足$a^r \equiv 1 (mod \ p)$,那么$\delta(p) | r$,由此推广,可知$\delta(p) | \phi(p)$,所以$p$的原根个数即为$p$之前与$\phi(p)$互质的数,即$\phi(p)$故定理成立
      - 若$g$是$m$的一个原根,则$g, \ g^1, \ g^2, \ ..., \ g^{\phi(m)} (mod \ p)$两两不同
    原根求法:
      将$\phi(m)$质因数分解,得$\phi(m) = p_1^{c_1} * p_2^{c_2} * ... * p_k^{c_k}$
      那么所有$g$满足$g^{\frac{\phi(m)}{p_i}} \neq 1 (mod \ m)$即为$m$的原根

- $NTT$

  由于$FTT$涉及到复数的运算,所以常数很大,而$NTT$仅需使用长整型,可大大优化常数

  能够将原根代替单位根进行计算,是因为它们的性质相似,至少在单位根需要的那几个性质原根都满足,当然,要能够进行$NTT$,需要满足模数$p$为质数,且$p = ax + 1$其中$x$为$2$的次幂,那么一般能满足条件的数(常用)有:
  $|\ \ \ \ \ \ \ \ \ \ \ \ p \ \ \ \ \ \ \ \ \ \ \ \ |\ \ \ \ g \ \ \ \ |$

  $|\ \ \ \ 469762049 \ \ \ \ |\ \ \ \ 3 \ \ \ \ |$

  $|\ \ \ \ 998244353 \ \ \ \ |\ \ \ \ 3 \ \ \ \ |$

  $|\ \ \ 1004535809 \ \ \ |\ \ \ \ 3 \ \ \ \ |$
  那么,就可以将单位根$\omega_n$替换为$g^{\frac{p - 1}{n}}$进行$NTT$了

- 代码 

 #include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath> #define MOD 998244353
#define g 3 using namespace std; typedef long long LL; const int MAXN = ( << ); LL power (LL x, int p) {
LL cnt = ;
while (p) {
if (p & )
cnt = cnt * x % MOD; x = x * x % MOD;
p >>= ;
} return cnt;
} const LL invg = power (g, MOD - ); int N, M;
LL A[MAXN], B[MAXN]; int oppo[MAXN];
int limit;
void NTT (LL* a, int inv) {
for (int i = ; i < limit; i ++)
if (i < oppo[i])
swap (a[i], a[oppo[i]]);
for (int mid = ; mid < limit; mid <<= ) {
LL ome = power (inv == ? g : invg, (MOD - ) / (mid << ));
for (int n = mid << , j = ; j < limit; j += n) {
LL x = ;
for (int k = ; k < mid; k ++, x = x * ome % MOD) {
LL a1 = a[j + k], xa2 = x * a[j + k + mid] % MOD;
a[j + k] = (a1 + xa2) % MOD;
a[j + k + mid] = (a1 - xa2 + MOD) % MOD;
}
}
}
} int getnum () {
int num = ;
char ch = getchar (); while (! isdigit (ch))
ch = getchar ();
while (isdigit (ch))
num = (num << ) + (num << ) + ch - '', ch = getchar (); return num;
} int main () {
N = getnum (), M = getnum ();
for (int i = ; i <= N; i ++)
A[i] = (int) getnum ();
for (int i = ; i <= M; i ++)
B[i] = (int) getnum (); int n, lim = ;
for (n = ; n <= N + M; n <<= , lim ++);
for (int i = ; i <= n; i ++)
oppo[i] = (oppo[i >> ] >> ) | ((i & ) << (lim - ));
limit = n;
NTT (A, );
NTT (B, );
for (int i = ; i <= n; i ++)
A[i] = A[i] * B[i] % MOD;
NTT (A, - );
LL invn = power (n, MOD - );
for (int i = ; i <= N + M; i ++) {
if (i)
putchar (' ');
printf ("%d", (int) (A[i] * invn % MOD));
}
puts (""); return ;
} /*
1 2
1 2
1 2 1
*/ /*
5 5
1 7 4 0 9 4
8 8 2 4 5 5
*/

NTT

- 任意模数$NTT$(三模数$NTT$法)

  有公式

$\left\{\begin{aligned} x \equiv a_1 (mod \ m_1) \\ x \equiv a_2 (mod \ m_2) \\ x \equiv a_3 (mod \ m_3) \end{aligned}\right.$

  直接乘会爆$long \ long$,就先将上面的用$CRT$合并,得

$\left\{\begin{aligned} x \equiv A (mod \ M) \\ x \equiv a_3 (mod \ m_3) \end{aligned}\right.$

  那么设$Ans = kM + A$,则有
  

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

  直接处理即可

- 代码(任意模数$NTT$)

 #include <iostream>
#include <cstdio>
#include <cstring> using namespace std; typedef long long LL; const int MAXN = ( << ); const LL MOD[]= {, , }; // 三模数
const LL g = ;
const long double eps = 1e-; LL multi (LL a, LL b, LL p) { // 快速乘
a %= p, b %= p;
return ((a * b - (LL) ((LL) ((long double) a / p * b + eps) * p)) % p + p) % p;
}
LL power (LL x, LL p, LL mod) {
LL cnt = ;
while (p) {
if (p & )
cnt = cnt * x % mod; x = x * x % mod;
p >>= ;
} return cnt;
}
const LL invg[]= {power (g, MOD[] - , MOD[]), power (g, MOD[] - , MOD[]), power (g, MOD[] - , MOD[])}; int N, M;
LL P; LL A[MAXN], B[MAXN]; int limit;
int oppo[MAXN];
void NTT (LL* a, int inv, int type) {
for (int i = ; i < limit; i ++)
if (i < oppo[i])
swap (a[i], a[oppo[i]]);
for (int mid = ; mid < limit; mid <<= ) {
LL ome = power (inv == ? g : invg[type], (MOD[type] - ) / (mid << ), MOD[type]);
for (int n = mid << , j = ; j < limit; j += n) {
LL x = ;
for (int k = ; k < mid; k ++, x = x * ome % MOD[type]) {
LL a1 = a[j + k], xa2 = x * a[j + k + mid] % MOD[type];
a[j + k] = (a1 + xa2) % MOD[type];
a[j + k + mid] = (a1 - xa2 + MOD[type]) % MOD[type];
}
}
}
} LL ntta[][MAXN], nttb[][MAXN];
void NTT_Main () {
int n, lim = ;
for (n = ; n <= N + M; n <<= , lim ++);
limit = n;
for (int i = ; i < n; i ++)
oppo[i] = (oppo[i >> ] >> ) | ((i & ) << (lim - ));
for (int i = ; i < ; i ++) {
for (int j = ; j < n; j ++)
ntta[i][j] = A[j];
for (int j = ; j < n; j ++)
nttb[i][j] = B[j];
NTT (ntta[i], , i);
NTT (nttb[i], , i);
for (int j = ; j < n; j ++)
ntta[i][j] = ntta[i][j] * nttb[i][j] % MOD[i];
NTT (ntta[i], - , i);
LL invn = power (n, MOD[i] - , MOD[i]);
for (int j = ; j <= N + M; j ++)
ntta[i][j] = ntta[i][j] * invn % MOD[i];
}
} LL ans[MAXN];
void CRT () {
LL m = MOD[] * MOD[];
LL M1 = MOD[], M2 = MOD[];
LL t1 = power (M1, MOD[] - , MOD[]), t2 = power (M2, MOD[] - , MOD[]), invM = power (m % MOD[], MOD[] - , MOD[]);
for (int i = ; i <= N + M; i ++) {
LL a1 = ntta[][i], a2 = ntta[][i], a3 = ntta[][i];
LL A = (multi (a1 * M1 % m, t1 % m, m) + multi (a2 * M2 % m, t2 % m, m)) % m;
LL k = ((a3 - A % MOD[]) % MOD[] + MOD[]) % MOD[] * invM % MOD[];
ans[i] = ((k % P * (m % P) % P + A % P) % P + P) % P;
}
} int getnum () {
int num = ;
char ch = getchar (); while (! isdigit (ch))
ch = getchar ();
while (isdigit (ch))
num = (num << ) + (num << ) + ch - '', ch = getchar (); return num;
} int main () {
N = getnum (), M = getnum (), P = (LL) getnum ();
for (int i = ; i <= N; i ++)
A[i] = (LL) getnum ();
for (int i = ; i <= M; i ++)
B[i] = (LL) getnum (); NTT_Main ();
CRT ();
for (int i = ; i <= N + M; i ++) {
if (i)
putchar (' ');
printf ("%lld", ans[i]);
}
puts (""); return ;
} /*
5 8 28
19 32 0 182 99 95
77 54 15 3 98 66 21 20 38
*/

任意模数NTT(三模数NTT法)

$NTT$(快速数论变换)的更多相关文章

  1. NTT 快速数论变换

    NTT 先学习FFT 由于FFT是使用复数运算,精度并不好,而且也无法取模,所以有了NTT(快速数论变换). 建议先完全理解FFT后再学习NTT. 原根 NTT使用与单位根性质相似的原根来代替单位根. ...

  2. [学习笔记]NTT——快速数论变换

    先要学会FFT[学习笔记]FFT——快速傅里叶变换 一.简介 FFT会爆精度.而且浮点数相乘常数比取模还大. 然后NTT横空出世了 虽然单位根是个好东西.但是,我们还有更好的东西 我们先选择一个模数, ...

  3. 模板 NTT 快速数论变换

    NTT裸模板,没什么好解释的 这种高深算法其实也没那么必要知道原理 #include <cstdio> #include <cstring> #include <algo ...

  4. 【算法】快速数论变换(NTT)初探

    [简介] 快速傅里叶变换(FFT)运用了单位复根的性质减少了运算,但是每个复数系数的实部和虚部是一个余弦和正弦函数,因此系数都是浮点数,而浮点数的运算速度较慢且可能产生误差等精度问题,因此提出了以数论 ...

  5. Algorithm: 多项式乘法 Polynomial Multiplication: 快速傅里叶变换 FFT / 快速数论变换 NTT

    Intro: 本篇博客将会从朴素乘法讲起,经过分治乘法,到达FFT和NTT 旨在能够让读者(也让自己)充分理解其思想 模板题入口:洛谷 P3803 [模板]多项式乘法(FFT) 朴素乘法 约定:两个多 ...

  6. 「算法笔记」快速数论变换(NTT)

    一.简介 前置知识:多项式乘法与 FFT. FFT 涉及大量 double 类型数据操作和 \(\sin,\cos\) 运算,会产生误差.快速数论变换(Number Theoretic Transfo ...

  7. 快速傅里叶变换 & 快速数论变换

    快速傅里叶变换 & 快速数论变换 [update 3.29.2017] 前言 2月10日初学,记得那时好像是正月十五放假那一天 当时写了手写版的笔记 过去近50天差不多忘光了,于是复习一下,具 ...

  8. JZYZOJ 2041 快速数论变换 NTT 多项式

    http://172.20.6.3/Problem_Show.asp?id=2041 https://blog.csdn.net/ggn_2015/article/details/68922404 代 ...

  9. 多项式乘法(FFT)模板 && 快速数论变换(NTT)

    具体步骤: 1.补0:在两个多项式最前面补0,得到两个 $2n$ 次多项式,设系数向量分别为 $v_1$ 和 $v_2$. 2.求值:用FFT计算 $f_1 = DFT(v_1)$ 和 $f_2=DF ...

随机推荐

  1. bzoj5123 [Lydsy12月赛]线段树的匹配

    题意: 线段树是这样一种数据结构:根节点表示区间 [1, n]:对于任意一个表示区间 [l, r] 的节点,若 l < r, 则取 mid = ⌊l+r/2⌋,该节点的左儿子为 [l, mid] ...

  2. git commad

    repo forall -c 'echo $REPO_PATH;ssh trd.git.htc.com -p29419 -lowen_wen gerrit create-project --submi ...

  3. word2013 如何套用模版

    文件-->选项-->加载项-->最下面下拉框选择“模版”-->点击转到

  4. Docker学习笔记三:Docker部署Java web系统

    Docker部署Java Web系统 1.在root目录下创建一个路径test/app mkdir test && cd test&& mkdir app && ...

  5. 洛谷 P4390 [BOI2007]Mokia 摩基亚 解题报告

    P4390 [BOI2007]Mokia 摩基亚 题目描述 摩尔瓦多的移动电话公司摩基亚(\(Mokia\))设计出了一种新的用户定位系统.和其他的定位系统一样,它能够迅速回答任何形如"用户 ...

  6. 升级系统后maxvim不能用,重新下载编译个

    1. 获取macvim源代码git clone https://github.com/b4winckler/macvim.git 2 配置及编译 编译选项 ./configure --with-fea ...

  7. APIO2017

    商旅 在广阔的澳大利亚内陆地区长途跋涉后,你孤身一人带着一个背包来到了科巴.你被这个城市发达而美丽的市场所 深深吸引,决定定居于此,做一个商人.科巴有个集市,集市用从1到N的整数编号,集市之间通过M条 ...

  8. 【bzoj2788】Festival

    Portal --> bzoj2788 Description 有\(n\)个正整数\(X_1,X_2,...,X_n\),再给出\(m1+m2\)个限制条件,限制分为两类: 1.给出\(a,b ...

  9. php使用时间戳保存时间的意义

    时间戳记录的是格林尼治时间,使用date格式化的时候会根据你程序设置的不同时区显示不同的时间. 如果使用具体时间,则还需要进行多一步转换.

  10. TCP粘包处理

    TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务.收发两端(客户端和服务器端)都要有一一成对的socket, 因此,发送端为了将多个 ...