$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 ...
随机推荐
- SolrPerformanceFactors--官方文档
原文地址:http://wiki.apache.org/solr/SolrPerformanceFactors Contents Schema Design Considerations indexe ...
- bzoj3517 翻硬币
题意 有一个n行n列的棋盘,每个格子上都有一个硬币,且n为偶数.每个硬币要么是正面朝上,要么是反面朝上.每次操作你可以选定一个格子(x,y),然后将第x行和第y列的所有硬币都翻面.求将所有硬币都变成同 ...
- 【bzoj5183】[Baltic2016]Park 离线+对偶图+并查集
题目描述 在Byteland的首都,有一个矩形围栏围起来的公园.在这个公园里树和访客都以一个圆形表示.公园有四个出入口,每个角落一个(1=左下角,2=右下角,3=右上角,4=左上角).访客能通过这些出 ...
- 吉哥系列故事――完美队形II HDU - 4513(马拉车变一下形)
题意: 求最长回文串...但这个回文串要符合从中间到两头 逐个递减 解析: 在扩散的时候加一个判断就好了 #include <iostream> #include <cstdio&g ...
- [pool www] user has not been defined
[02-Dec-2014 00:28:58] ALERT: [pool www] user has not been defined [02-Dec-2014 00:28:58] ERROR: fai ...
- bzoj2396: 神奇的矩阵(矩阵乘法+随机化)
这题n三方显然会GG... 运用矩阵乘法的性质A*B*R=A*(B*R)=C*R,于是随机化出一个一列的R,就可以把复杂度降低成n方...大概率是不会错的 #include<iostream&g ...
- 嘘,如何激活更新的win10
win10更新了,所以很坑的是以前的密钥又不管用了,系统和office都要重新激活,然而微软的更新就是很有恶意的,总之成功率堪忧. 还好看到了万能的网友的办法. slmgr.vbs /upk slmg ...
- JSP2 的自定义标签
在 JSP 中开发标签库只需如下几个步骤 1.开发自定义标签处理类 2.建立一个 *.tld 文件,每个 *.tld 文件对应一个标签库,每个标签库可包含多个标签 3.在 JSP 文件中使用自定义标签 ...
- 手脱nSPack 1.3
1.PEID查壳 nSPack 1.3 -> North Star/Liu Xing Ping 2.载入OD,pushad下面的call哪里使用ESP定律,下硬件访问断点,然后shift+F9运 ...
- C++ ------ 创建对象 new 和不 new 的区别
1.作用域不同 不用new:作用域限制在定义类对象的方法中,当方法结束时,类对象也被系统释放了,(安全不会造成内存系统泄漏). 用new:创建的是指向类对象的指针,作用域变成了全局,当程序结束时,必须 ...