$NTT$(快速数论变换)
- 概念引入
- 阶
对于$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$法)
有公式
直接乘会爆$long \ long$,就先将上面的用$CRT$合并,得
那么设$Ans = kM + A$,则有
直接处理即可
- 代码(任意模数$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$(快速数论变换)的更多相关文章
- NTT 快速数论变换
NTT 先学习FFT 由于FFT是使用复数运算,精度并不好,而且也无法取模,所以有了NTT(快速数论变换). 建议先完全理解FFT后再学习NTT. 原根 NTT使用与单位根性质相似的原根来代替单位根. ...
- [学习笔记]NTT——快速数论变换
先要学会FFT[学习笔记]FFT——快速傅里叶变换 一.简介 FFT会爆精度.而且浮点数相乘常数比取模还大. 然后NTT横空出世了 虽然单位根是个好东西.但是,我们还有更好的东西 我们先选择一个模数, ...
- 模板 NTT 快速数论变换
NTT裸模板,没什么好解释的 这种高深算法其实也没那么必要知道原理 #include <cstdio> #include <cstring> #include <algo ...
- 【算法】快速数论变换(NTT)初探
[简介] 快速傅里叶变换(FFT)运用了单位复根的性质减少了运算,但是每个复数系数的实部和虚部是一个余弦和正弦函数,因此系数都是浮点数,而浮点数的运算速度较慢且可能产生误差等精度问题,因此提出了以数论 ...
- Algorithm: 多项式乘法 Polynomial Multiplication: 快速傅里叶变换 FFT / 快速数论变换 NTT
Intro: 本篇博客将会从朴素乘法讲起,经过分治乘法,到达FFT和NTT 旨在能够让读者(也让自己)充分理解其思想 模板题入口:洛谷 P3803 [模板]多项式乘法(FFT) 朴素乘法 约定:两个多 ...
- 「算法笔记」快速数论变换(NTT)
一.简介 前置知识:多项式乘法与 FFT. FFT 涉及大量 double 类型数据操作和 \(\sin,\cos\) 运算,会产生误差.快速数论变换(Number Theoretic Transfo ...
- 快速傅里叶变换 & 快速数论变换
快速傅里叶变换 & 快速数论变换 [update 3.29.2017] 前言 2月10日初学,记得那时好像是正月十五放假那一天 当时写了手写版的笔记 过去近50天差不多忘光了,于是复习一下,具 ...
- JZYZOJ 2041 快速数论变换 NTT 多项式
http://172.20.6.3/Problem_Show.asp?id=2041 https://blog.csdn.net/ggn_2015/article/details/68922404 代 ...
- 多项式乘法(FFT)模板 && 快速数论变换(NTT)
具体步骤: 1.补0:在两个多项式最前面补0,得到两个 $2n$ 次多项式,设系数向量分别为 $v_1$ 和 $v_2$. 2.求值:用FFT计算 $f_1 = DFT(v_1)$ 和 $f_2=DF ...
随机推荐
- BZOJ 2299 向量(裴蜀定理)
题意:给你一对数a,b,你可以任意使用(a,b), (a,-b), (-a,b), (-a,-b), (b,a), (b,-a), (-b,a), (-b,-a)这些向量,问你能不能拼出另一个向量(x ...
- Qt 事件处理机制
Qt 事件处理机制 因为这篇文章写得特别好,将Qt的事件处理机制能够阐述的清晰有条理,并且便于学习.于是就装载过来了(本文做了排版,并删减了一些冗余的东西,希望原主勿怪),以供学习之用. 简介 在Qt ...
- hive 导入数据
1.load data load data local inpath "/home/hadoop/userinfo.txt" into table userinfo; " ...
- Java 工作2年后需要达到怎么样的技术水平
有人回答说这只能是大企业或者互联网企业的工程师才能拿到.也许是的,小公司或者非互联网企业拿两万的不太可能是码农了,应该是已经转管理后才有可能.还有区域问题,这个不在我的考虑范围内,因为除了北上广深杭, ...
- 洛谷 P1278 单词游戏 【状压dp】
题目描述 Io和Ao在玩一个单词游戏. 他们轮流说出一个仅包含元音字母的单词,并且后一个单词的第一个字母必须与前一个单词的最后一个字母一致. 游戏可以从任何一个单词开始. 任何单词禁止说两遍,游戏中只 ...
- SQLite中的自增关键字:AUTO_INCREMENT、INTEGER PRIMARY KEY与AUTOINCREMENT
1.SQLite不支持关键字AUTO_INCREMENT 1)AUTO_INCREMENT不生效的问题 SQL语句: CREATE TABLE todo ( id INTEGER AUTO_I ...
- 【题解】Inspection UVa 1440 LA 4597 NEERC 2009
题目传送门:https://vjudge.net/problem/UVA-1440 看上去很像DAG的最小路径覆盖QwQ? 反正我是写了一个上下界网络流,建模方法清晰易懂. 建立源$s$,向每个原图中 ...
- C语言基本类型的字节数
- OpenCV---图像梯度
图像梯度 推文:[OpenCV入门教程之十二]OpenCV边缘检测:Canny算子,Sobel算子,Laplace算子,Scharr滤波器合辑 图像梯度可以把图像看成二维离散函数,图像梯度其实就是这个 ...
- Jenkins使用教程之创建job
第一部分:常规设置(general) 1.点击新建,进入新建项目页面 2.输入项目名称,选择构建一个自由风格的软件项目(里面可以自己自由进行配置,使用更加灵活),点击ok 3.项目名称与描述 4.gi ...