WIKIOI
wiki(I:)
比赛相关
工具软件
语言基础
算法基础
搜索
动态规划
字符串
数学
数据结构
图论
计算几何
杂项
专题
数学部分简介
符号
复数
位运算
快速幂
进位制
高精度计算
平衡三进制
素数
最大公约数
欧拉函数
筛法
欧拉定理 & 费马小定理
类欧几里德算法
裴蜀定理
乘法逆元
线性同余方程
中国剩余定理
二次剩余
BSGS
原根
卢卡斯定理
莫比乌斯反演
杜教筛
Min_25 筛
分解质因数
多项式部分简介
拉格朗日插值
快速傅里叶变换
快速数论变换
快速沃尔什变换
多项式求逆
多项式开方
多项式除法|取模
多项式对数函数|指数函数
多项式牛顿迭代
多项式多点求值|快速插值
多项式三角函数
多项式反三角函数
常系数齐次线性递推
生成函数简介
普通生成函数
指数生成函数
向量
矩阵
高斯消元
线性基
线性规划简介
单纯形算法
排列组合
卡特兰数
斯特林数
贝尔数
伯努利数
康托展开
容斥原理
抽屉原理
概率初步
置换群
斐波那契数列
博弈论
牛顿迭代法
数值积分
分段打表
64 objects
本站非官方,所收集资源均来源于网络。
快速数论变换 - 数学
author: ChungZH, Yukimaikoriya (本文转载自 [桃酱的算法笔记](https://zhuanlan.zhihu.com/c_1005817911142838272) ,原文戳 [链接](https://zhuanlan.zhihu.com/p/41867199) ,已获得作者授权) ## 简介 NTT 解决的是多项式乘法带模数的情况,可以说有些受模数的限制,数也比较大, 但是它比较方便呀毕竟没有复数部分 ## 学习 NTT 之前…… ### 生成子群 子群:群 $(S,⊕), (S′,⊕)$ ,满足 $S′⊂S$ ,则 $(S′,⊕)$ 是 $(S,⊕)$ 的子群 拉格朗日定理: $|S′|∣|S |$ 证明需要用到陪集,得到陪集大小等于子群大小,每个陪集要么不相交要么相等,所有陪集的并是集合 $S$ ,那么显然成立。 生成子群: $a \in S$ 的生成子群 $\left
= \\{a^{(k)}, k \geq 1 \\}$ , $a$ 是 $\left< a \right>$ 的生成元 阶:群 $S$ 中 $a$ 的阶是满足 $a^r=e$ 的最小的 $r$ ,符号 $\operatorname{ord}(a)$ ,有 $\operatorname{ord}(a)=\left|\left
\right|$ ,显然成立。 考虑群 $Z_n^ \times =\\{[a], n \in Z_n : \gcd(a, n) = 1\\}, |Z_n^ \times | = \varphi(n)$ 阶就是满足 $a^r \equiv 1 \pmod n$ 的最小的 $r$ , $\operatorname{ord}(a)=r$ ### [原根](/primitive-root/) $g$ 满足 $\operatorname{ord}_n(g)=\left|Z_n^\times\right|=\varphi(n)$ ,对于质数 $p$ ,也就是说 $g^i \bmod p, 0 \leq i < p$ 结果互不相同。 模 $n$ 有原根的充要条件 : $n = 2, 4, p^e, 2 \times p^e$ 离散对数: $g^t \equiv a \pmod n,ind_{n,g}{(a)}=t$ 因为 $g$ 是原根,所以 $gt$ 每 $\varphi(n)$ 是一个周期,可以取到 $| Z \times n |$ 的所有元素 对于 $n$ 是质数时,就是得到 $[1,n−1]$ 的所有数,就是 $[0,n−2]$ 到 $[1,n−1]$ 的映射 离散对数满足对数的相关性质,如 求原根可以证明满足 $g^r \equiv 1\pmod p$ 的最小的 $r$ 一定是 $p−1$ 的约数 对于质数 $p$ ,质因子分解 $p−1$ ,若 $g^{(p-1)/pi} \neq 1 \pmod p$ 恒成立, $g$ 为 $p$ 的原根 ## NTT 对于质数 $p=qn+1, (n=2^m)$ , 原根 $g$ 满足 $g^{qn} \equiv 1 \pmod p$ , 将 $g_n=g^p\pmod q$ 看做 $\omega_n$ 的等价,择其满足相似的性质,比如 $g_n^n \equiv 1 \pmod p, g_n^{n/2} \equiv -1 \pmod p$ 然后因为这里涉及到数论变化,所以这里的 $N$ (为了区分 FFT 中的 $n$ ,我们把这里的 $n$ 称为 $N$ )可以比 FFT 中的 $n$ 大,但是只要把 $\frac{qN}{n}$ 看做这里的 $q$ 就行了,能够避免大小问题…… 常见的有 $$ p = 1004535809 = 479 \times 2^{21}+1, g=3 $$ $$ p=998244353=7 \times 17 \times 2^{23}+1, g=3 $$ 就是 $g^{qn}$ 的等价 $e^{2\pi n}$ 迭代到长度 $l$ 时 $g_l = g^{\frac{p-1}{l}}$ ,或者 $\omega_n = g_l = g_N^{\frac{N}{l}} = g_N^{\frac{p-1}{l}}$ 接下来放一个大数相乘的模板 参考网址如下
```cpp #include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std; inline int read() { int x = 0, f = 1; char ch = getchar(); while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while (ch <= '9' && ch >= '0') { x = 10 * x + ch - '0'; ch = getchar(); } return x * f; } void print(int x) { if (x < 0) putchar('-'), x = -x; if (x >= 10) print(x / 10); putchar(x % 10 + '0'); } const int N = 300100, P = 998244353; inline int qpow(int x, int y) { int res(1); while (y) { if (y & 1) res = 1ll * res * x % P; x = 1ll * x * x % P; y >>= 1; } return res; } int r[N]; void ntt(int *x, int lim, int opt) { register int i, j, k, m, gn, g, tmp; for (i = 0; i < lim; ++i) if (r[i] < i) swap(x[i], x[r[i]]); for (m = 2; m <= lim; m <<= 1) { k = m >> 1; gn = qpow(3, (P - 1) / m); for (i = 0; i < lim; i += m) { g = 1; for (j = 0; j < k; ++j, g = 1ll * g * gn % P) { tmp = 1ll * x[i + j + k] * g % P; x[i + j + k] = (x[i + j] - tmp + P) % P; x[i + j] = (x[i + j] + tmp) % P; } } } if (opt == -1) { reverse(x + 1, x + lim); register int inv = qpow(lim, P - 2); for (i = 0; i < lim; ++i) x[i] = 1ll * x[i] * inv % P; } } int A[N], B[N], C[N]; char a[N], b[N]; int main() { register int i, lim(1), n; scanf("%s", &a); n = strlen(a); for (i = 0; i < n; ++i) A[i] = a[n - i - 1] - '0'; while (lim < (n << 1)) lim <<= 1; scanf("%s", &b); n = strlen(b); for (i = 0; i < n; ++i) B[i] = b[n - i - 1] - '0'; while (lim < (n << 1)) lim <<= 1; for (i = 0; i < lim; ++i) r[i] = (i & 1) * (lim >> 1) + (r[i >> 1] >> 1); ntt(A, lim, 1); ntt(B, lim, 1); for (i = 0; i < lim; ++i) C[i] = 1ll * A[i] * B[i] % P; ntt(C, lim, -1); int len(0); for (i = 0; i < lim; ++i) { if (C[i] >= 10) len = i + 1, C[i + 1] += C[i] / 10, C[i] %= 10; if (C[i]) len = max(len, i); } while (C[len] >= 10) C[len + 1] += C[len] / 10, C[len] %= 10, len++; for (i = len; ~i; --i) putchar(C[i] + '0'); puts(""); return 0; } ```