FFT可以用来计算多项式乘法,但是复数的运算中含有大量的浮点数,精度较低。对于只有整数参与运算的多项式,有时,\(\text{NTT(Number-Theoretic Transform)}\)会是更好的选择。
阶
若\(a,p\)互素,且\(p>1\),对于\(a^k \equiv 1 (\mod p)\)的最小的\(k\),称为\(a\)模\(p\)的阶,记做\(\sigma_p(a)\)。
\(E.g.\) \(\sigma_7(2)=3\)
\(2^1\equiv 2(\mod 7)\)
\(2^2\equiv 4(\mod 7)\)
\(2^3\equiv 1(\mod 7)\)
对于一个数\(g\),\(g\)的阶一定是\(p-1\)的约数。
证明:
假设最小的\(k\)不是\(p-1\)的约数,找到\(x\)满足\(xk>p-1>(x-1)k\),由费马小定理可知
\]
\(xk-(p-1)<k\),与假设矛盾。
原根
定义
\(FFT\)中,我们使用单位复根\(\omega_n^k=\cos k\frac{2\pi}{n}+i\sin k\frac{2\pi}{n}\),那有没有什么能够代替单位复根且解决精度问题呢?这就是原根。
设\(m\)是正整数,\(a\)是整数,若\(a\)模\(m\)的阶等于\(\varphi(m)\),则称\(a\)为模\(m\)的一个原根。
若\(p\)为质数,设\(g\)为\(p\)的原根,那么\(g^i \mod p(1<j<p,1\le i\le p-1)\)的结果两两不同。且其等价于\(g^{p-1}\equiv 1(\mod p)\)当且仅当指数为\(p-1\)的时候成立。(这里\(p\)是素数)
简单证明一下:
显然\(g^0 \equiv 1(\mod p)\)
由原根的定义可知满足\(g^{i} \equiv 1(\mod p)\)的最小正整数为\(\varphi(p)=p-1\)
故由指数循环节可知,\(g^i \mod p(1<j<p,1\le i\le p-1)\)的结果两两不同。
性质
考虑在FFT当中我们需要单位复根的以下性质:
\(\omega_n^t\)互不相同,保证点值的合法性;
\(\omega_{2n}^{2k} = \omega_n^k\),用于分治;
\(\omega_n^{k+\frac{n}{2}} = -\omega_n^k\),用于分治;
当\(k\neq 0\)时,\(1+\omega_n^k+(\omega_n^k)^2+\dots +(\omega_n^k)^{n-1}=0\),用于逆变换。
性质一
令\(\omega_n=g^q\),\(1,g^q,g^{2q},\dots,g^{(n-1)q}\)互不相同,满足性质一。
性质二
由\(\omega_n = g^q\)可知,\(\omega_{2n}=g^{\frac{q}{2}}(p=\frac{q}{2} \times 2n + 1)\),故\(\omega_{2n}^{2k} = g^{2k \frac{q}{2}}=g^{kq}=g^q\),满足性质二。
性质三
根据费马小定理得
\]
又因为\((\omega_n^{\frac{n}{2}})^2=\omega_n^n\),所以\(\omega_n^{\frac{n}{2}}\equiv \pm 1 (\mod p)\),而根据性质一可得\(\omega_n^{\frac{n}{2}}\neq \omega_n^0\),即\(\omega_n^{\frac{n}{2}}\equiv -1(\mod p)\)。可推出\(\omega_n^{k+\frac{n}{2}}=\omega_n^k \times \omega_n^{\frac{n}{2}}=-\omega_n^k (\mod p)\),满足性质三。
性质四
当\(k\neq 0\)时
\]
\]
\]
\]
由性质三中的推论可知,\((\omega_n^k)^n-1=(\omega_n^n)^k-1\equiv \omega_n^n-1\equiv 0 (\mod p)\),故\(S(\omega_n^k)=0\),性质四成立。
求原根
求一个质数的原根,可以用枚举法——枚举\(g\),检验\(g\)是否为\(p\)的原根。
根据前面的关于阶知识可知,检验时,只需枚举\(p-1\)的所有约数\(q\),检验\(g^q\equiv 1(\mod p)\)即可。
代码实现
将\(FFT\)里所有关于\(\omega_n\)的运算替换成\(g^q\)在模意义下的运算即可,注意\(\div n\)要改为\(\times n^{-1}\)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
inline ll ty() {
char ch = getchar(); ll x = 0, f = 1;
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
return x * f;
}
const int _ = 4e6 + 10;
const ll P = 998244353, G = 3, Gx = 332748118;
int N, M, r[_];
ll A[_], B[_];
ll ksm(ll a, ll b) {
ll ret = 1;
for ( ; b; b >>= 1) {
if (b & 1) ret = ret * a % P;
a = a * a % P;
}
return ret;
}
void ntt(int lim, ll *a, int op) {
for (int i = 0; i < lim; ++i)
if (i < r[i]) swap(a[i], a[r[i]]);
for (int len = 2; len <= lim; len <<= 1) {
int mid = len >> 1;
ll Wn = ksm(op == 1 ? G : Gx, (P - 1) / len);
for (int i = 0; i < lim; i += len) {
ll w = 1;
for (int j = 0; j < mid; ++j, w = (w * Wn) % P) {
ll x = a[i + j], y = w * a[i + j + mid] % P;
a[i + j] = (x + y) % P;
a[i + j + mid] = (x - y + P) % P;
}
}
}
}
int main() {
#ifndef ONLINE_JUDGE
freopen("ntt.in", "r", stdin);
freopen("ntt.out", "w", stdout);
#endif
N = ty(), M = ty();
for (int i = 0; i <= N; ++i) A[i] = (ty() + P) % P;
for (int i = 0; i <= M; ++i) B[i] = (ty() + P) % P;
int lim = 1, k = 0;
while (lim <= N + M) lim <<= 1, ++k;
for (int i = 0; i < lim ; ++i) r[i] = (r[i >> 1] >> 1) | ((i & 1) << (k - 1));
ntt(lim, A, 1);
ntt(lim, B, 1);
for (int i = 0; i < lim; ++i) A[i] = (A[i] * B[i]) % P;
ntt(lim, A, -1);
ll invx = ksm(lim, P - 2);
for (int i = 0; i <= N + M; ++i)
printf("%lld ", (A[i] * invx) % P);
return 0;
}
参考资料
从傅里叶变换(FFT)到数论变换(NTT)的更多相关文章
-
Algorithm: 多项式乘法 Polynomial Multiplication: 快速傅里叶变换 FFT / 快速数论变换 NTT
Intro: 本篇博客将会从朴素乘法讲起,经过分治乘法,到达FFT和NTT 旨在能够让读者(也让自己)充分理解其思想 模板题入口:洛谷 P3803 [模板]多项式乘法(FFT) 朴素乘法 约定:两个多 ...
-
多项式 之 快速傅里叶变换(FFT)/数论变换(NTT)/常用套路【入门】
原文链接https://www.cnblogs.com/zhouzhendong/p/Fast-Fourier-Transform.html 多项式 之 快速傅里叶变换(FFT)/数论变换(NTT)/ ...
-
【算法】快速数论变换(NTT)初探
[简介] 快速傅里叶变换(FFT)运用了单位复根的性质减少了运算,但是每个复数系数的实部和虚部是一个余弦和正弦函数,因此系数都是浮点数,而浮点数的运算速度较慢且可能产生误差等精度问题,因此提出了以数论 ...
-
快速傅里叶变换(FFT)学习笔记(其二)(NTT)
再探快速傅里叶变换(FFT)学习笔记(其二)(NTT) 目录 再探快速傅里叶变换(FFT)学习笔记(其二)(NTT) 写在前面 一些约定 前置知识 同余类和剩余系 欧拉定理 阶 原根 求原根 NTT ...
-
快速傅里叶变换FFT&; 数论变换NTT
相关知识 时间域上的函数f(t)经过傅里叶变换(Fourier Transform)变成频率域上的F(w),也就是用一些不同频率正弦曲线的加 权叠加得到时间域上的信号. \[ F(\omega)=\m ...
-
高速数论变换(NTT)
今天的A题.裸的ntt,但我不会,于是白送了50分. 于是跑来学一下ntt. 题面非常easy.就懒得贴了,那不是我要说的重点. 重点是NTT,也称高速数论变换. 在非常多问题中,我们可能会遇到在模意 ...
-
快速数论变换(NTT)小结
NTT 在FFT中,我们需要用到复数,复数虽然很神奇,但是它也有自己的局限性--需要用double类型计算,精度太低 那有没有什么东西能够代替复数且解决精度问题呢? 这个东西,叫原根 原根 阶 若\( ...
-
JZYZOJ 2041 快速数论变换 NTT 多项式
http://172.20.6.3/Problem_Show.asp?id=2041 https://blog.****.net/ggn_2015/article/details/68922404 代 ...
-
[快速数论变换 NTT]
先粘一个模板.这是求高精度乘法的 #include <bits/stdc++.h> #define maxn 1010 using namespace std; char s[maxn]; ...
随机推荐
-
ASP.NET WebApi OWIN 实现 OAuth 2.0
OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用. OAuth 允许用户提供一个令牌, ...
-
data:image/png;base64是什么
大家可能注意到了,网页上有些图片的src或css背景图片的url后面跟了一大串字符,比如: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJ ...
-
Oracle - SQL 错误: ORA-00917: 缺失逗号
ORACLE SQL语句中int型插入数据库时,不要加引号.
-
C# 几种常见的排序方法
1.冒泡排序 //冒泡排序 public void BubbleSort(int[] list) { int i, j, temp; bool done = false; j = ; while (( ...
-
ios 概况了解
iOS的系统架构分为四个层次:( iOS是基于UNIX内核,android是基于Linux内核) 核心操作系统层(Core OS layer).核心服务层(Core Services layer).媒 ...
-
SpringBoot-SpringMvc的Interceptor拦截器配置
Interceptor拦截器实现对每一个用户请求处理前后的业务处理,比如我们需要对用户请求进行响应时间的记录,需要记录请求从开始到结束所耗的时间,这时我们就需要用到拦截器了 下面我们以记录请求处理时间 ...
-
PAT A1059
PAT A1059 标签(空格分隔): PAT 解题思路 :先打印出素数表.利用结构体数组来存贮质因子的值和个数 strcut factor{ int x; //值 int cnt; //个数 }fa ...
-
hibernate 解决 org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
这是因为没有设置要更新的主键导致的,只要设置了要更新的主键就能更新成功(没有主键当然不能更新)
-
[HNOI2010]物品调度
题目描述 现在找工作不容易,Lostmonkey费了好大劲才得到fsk公司基层流水线操作员的职位.流水线上有n个位置,从0到n-1依次编号,一开始0号位置空,其它的位置i上有编号为i的盒子.Lostm ...
-
Html的简单学习笔记
1.Html简介 1)什么是html: HyperText Markup Language:超文本标记语言,网页语言. >超文本:超出文本范围. >标记: html中所有的操作都是使用标记 ...