快速构造FFT/NTT
@(学习笔记)[FFT, NTT]
问题概述
给出两个次数为\(n\)的多项式\(A\)和\(B\), 要求在\(O(n \log n)\)内求出它们的卷积, 即对于结果\(C\)的每一项, 都有$$c_i = \sum_{j = 0}^{n}a_j \cdot b_{i - j}$$
问题求解
大致思路
- 朴素做法: 考虑按照上面的式子暴力运算, 时间复杂度: \(O(n^2)\)
- 考虑把多项式化作点值表达, 记$$A(x) =\sum_{i = 0}^n a_i x^i$$ 我们把\(A\)和\(B\)的点值表达乘起来, 得到的就是\(C\)的点值表达, 即$$A(x) \cdot B(x) = C(x)$$
- 我们把\(x \to A(x)\)的运算称作是DFT(离散傅立叶变换Discrete Fourier Transform)
- 对于一个次数为\(n\)的多项式, 我们有它的\(n\)组不同点值表达, 通过点值表达求出原多项式的每一项的运算, 我们称之为IDFT(逆傅立叶变换)
DFT
考虑两个次数为\(n\)的多项式卷积, 得到的结果次数最高达到了\(2n - 1\). 所以我们至少需要\(2n - 1\)个结果的点值表达, 才足够把结果逆推出来(Hint: 为什么是\(2n-1\)个点值表达? 大体上可以从拉格朗日插值法来理解.).
考虑如何化简运算.
我们把多项式\(A\)拆分开奇数位和偶数位, 来计算它的点值表达. 我们令\(x_k\)为代入多项式计算的第\(k\)个值, 记$$f_0(x_k) = a_0 x_k^0 + a_2 x_k^1 + a_4 x_k^2 + ... + a_{2m} x_k^m$$
\]
则我们发现原多项式可以被表示作$$f(x_k) = f_0(x_k^2) + x_k \cdot f_1(x_k^2)$$
这样, 求原来长度为\(len\)的多项式的点值表达, 就变成求2个长度为\(\frac{len}{2}\)的多项式的点值表达.
我们还注意到, 这里代入\(f_0\)和\(f_1\)计算的值为\(x_k^2\). 假如我们代入的\(x_i\)和\(x_j\)满足\(x_i^2 = x_j^2\)且\(x_i \ne x_j\), 则只需要在\(f_0\)和\(f_1\)中代入一个值进行运算, 再分别把\(f_1\)分别乘上\(x_i\)和\(x_j\), 就可以一次处理出\(f(x_i)\)和\(f(x_j)\)两个的结果. 这种优化手段就是FFT和NTT的基本思想.
考虑如何构造\(x_i^2 = x_j^2\).
这里我们以NTT为例. 在数论意义下, 根据费马小定理, 有$$g^{p - 1} \equiv 1 \mod p: p \in 素数$$.
当我们要代入\(n\)个值计算多项式的点值表达时, 令\(x_0 = 1, x_1 = g^{\frac{p - 1}{n}} ... x_k = g^{\frac{p - 1}{n} \cdot k}\), 则有$$x_{k + \frac{n}{2}}^2 = \left( \left(g^{\frac{p - 1}{n}} \right)^{k + \frac{n}{2}} \right)^2 = \left( g^{\frac{p - 1}{n} \cdot k} \right)^2 \cdot g^{p - 1} \equiv \left( g^{\frac{p - 1}{n} \cdot k} \right)^2 = x_k^2 \mod p$$
则每个\(x_k\)都可以与\(x_{k + \frac{n}{2}}\)分为一组, 一起计算.
这样, 我们就可以在\(O(n \log n)\)内求出所需要的\(n\)个点值表达.
IDFT
我们把得到的点值表达看作是一个多项式, 再按照上面的DFT的做法搞一次, 得到这个点值表达的点值表达(大雾). 把每个点值表达都除以点值的个数, 即得到了\(C\)的每一项.
不会证.
结束.
Code
#include <cstdio>
#include <cctype>
#include <algorithm>
const int N = (int)5e4, P = 998244353, G = 3;
namespace Zeonfai
{
inline int getInt()
{
int sgn = 1, a = 0;
char c;
while(! isdigit(c = getchar()))
if(c == '-')
sgn *= -1;
while(isdigit(c))
a = a * 10 + c - '0', c = getchar();
return a * sgn;
}
}
namespace convolution
{
const int DEG = N << 2;
int deg, rev[DEG], omega[DEG], inv[DEG];
inline int modPower(int a, int x)
{
int res = 1;
for(; x; a = (long long)a * a % P, x >>= 1)
if(x & 1)
res = (long long) res * a % P;
return res;
}
inline void pretreat(int n, int m)
{
int sum = n + m;
deg = 1;
int bit = 0;
for(; deg < sum; deg <<= 1, ++ bit);
rev[0] = 0;
for(int i = 1; i < deg; ++ i)
rev[i] = rev[i >> 1] >> 1 | (i & 1) << bit - 1;
for(int i = 0; 1 << i <= deg; ++ i)
omega[i] = modPower(G, (P - 1) / (1 << i)), inv[i] = modPower(omega[i], P - 2);
}
inline void NTT(int *a, int opt)
{
for(int i = 0; i < deg; ++ i)
if(rev[i] < i)
std::swap(a[i], a[rev[i]]);
int cnt = 0;
for(int i = 2; i <= deg; i <<= 1)
{
++ cnt;
int curOmega = ~ opt ? omega[cnt] : inv[cnt];
for(int j = 0; j < deg; j += i)
{
int omega = 1;
for(int k = j; k < j + i / 2; ++ k)
{
int u = a[k], t = (long long)omega * a[k + i / 2] % P;
a[k] = (u + t) % P, a[k + i / 2] = (u - t + P) % P;
omega = (long long)omega * curOmega % P;
}
}
}
if(opt == -1)
{
int inv = modPower(deg, P - 2);
for(int i = 0; i < deg; ++ i)
a[i] = (long long)a[i] * inv % P;
}
}
inline void work(int *a, int n, int *b, int m)
{
pretreat(n, m);
NTT(a, 1), NTT(b, 1);
for(int i = 0; i < deg; ++ i)
a[i] = (long long)a[i] * b[i] % P;
NTT(a, -1);
for(int i = 0; i <= n + m; ++ i)
printf("%d ", a[i]);
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("polynomial.in", "r", stdin);
freopen("polynomial.out", "w", stdout);
#endif
using namespace Zeonfai;
int n = getInt(), m = getInt(), tp = getInt();
static int a[N << 2], b[N << 2];
for(int i = 0; i <= n; ++ i)
a[i] = getInt();
for(int i = 0; i <= m; ++ i)
b[i] = getInt();
convolution::work(a, n, b, m);
}
快速构造FFT/NTT的更多相关文章
- 快速傅里叶变换FFT / NTT
目录 FFT 系数表示法 点值表示法 复数 DFT(离散傅里叶变换) 单位根的性质 FFT(快速傅里叶变换) IFFT(快速傅里叶逆变换) NTT 阶 原根 扩展知识 FFT 参考blog: 十分简明 ...
- [模板] 快速傅里叶变换/FFT/NTT
简介 FFT是多项式乘法的一种快速算法, 时间复杂度 \(O(n \log n)\). FFT可以用于求解形如\(C_i = \sum_{j=0}^i A_jB_{i-j}\)的式子. 如果下标有偏差 ...
- 多项式 之 快速傅里叶变换(FFT)/数论变换(NTT)/常用套路【入门】
原文链接https://www.cnblogs.com/zhouzhendong/p/Fast-Fourier-Transform.html 多项式 之 快速傅里叶变换(FFT)/数论变换(NTT)/ ...
- Algorithm: 多项式乘法 Polynomial Multiplication: 快速傅里叶变换 FFT / 快速数论变换 NTT
Intro: 本篇博客将会从朴素乘法讲起,经过分治乘法,到达FFT和NTT 旨在能够让读者(也让自己)充分理解其思想 模板题入口:洛谷 P3803 [模板]多项式乘法(FFT) 朴素乘法 约定:两个多 ...
- 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT)
再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT) 目录 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Blueste ...
- 快速傅里叶变换FFT& 数论变换NTT
相关知识 时间域上的函数f(t)经过傅里叶变换(Fourier Transform)变成频率域上的F(w),也就是用一些不同频率正弦曲线的加 权叠加得到时间域上的信号. \[ F(\omega)=\m ...
- 快速傅里叶变换(FFT)学习笔记(其二)(NTT)
再探快速傅里叶变换(FFT)学习笔记(其二)(NTT) 目录 再探快速傅里叶变换(FFT)学习笔记(其二)(NTT) 写在前面 一些约定 前置知识 同余类和剩余系 欧拉定理 阶 原根 求原根 NTT ...
- 模板 - 数学 - 快速傅里叶变换/快速数论变换(FFT/NTT)
先看看. 通常模数常见的有998244353,1004535809,469762049,这几个的原根都是3.所求的项数还不能超过2的23次方(因为998244353的分解). 感觉没啥用. #incl ...
- [学习笔记&教程] 信号, 集合, 多项式, 以及各种卷积性变换 (FFT,NTT,FWT,FMT)
目录 信号, 集合, 多项式, 以及卷积性变换 卷积 卷积性变换 傅里叶变换与信号 引入: 信号分析 变换的基础: 复数 傅里叶变换 离散傅里叶变换 FFT 与多项式 \(n\) 次单位复根 消去引理 ...
随机推荐
- STM32——PWM基本知识及配置过程
将通用定时器分为四个部分: 1,选择时钟 2,时基电路 3,输入捕获 4,输出比较 本节定时器PWM输出主要涉及到定时器框图右下方部分,即输出比较部分 和上一讲相同,时基时钟来源于内部默认时钟 对此有 ...
- BZOJ 5299: [Cqoi2018]解锁屏幕
状压DP #include<cstdio> using namespace std; const int mod=1e8+7; int F[1000005][25],dis[25][25] ...
- phpcms 后台也名称
announce 公告 show.html 内容页 comment 评论 show_list.html 内容页评论列表 list.html 评论列表 content 内容模型 category.htm ...
- set(集合)类
1.set(集合)和 map(映射) 都属于关联容器,它们都支持查询一个元素是否存在,并能有效地获取元素.实现了红黑树的平衡二叉检索树的数据结构,插入元素时,它会自动调整二叉树的排列,把元素放到适当的 ...
- google浏览器audits
功能翻译 audits,审计 Audits help you identify and fix common problems that affect your site'sperformance,a ...
- 九度oj 题目1452:搬寝室
题目描述: 搬寝室是很累的,xhd深有体会.时间追述2006年7月9号,那天xhd迫于无奈要从27号楼搬到3号楼,因为10号要封楼了.看着寝室里的n件物品,xhd开始发呆,因为n是一个小于2000的整 ...
- 九度oj 题目1370:数组中出现次数超过一半的数字
题目描述: 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字.例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}.由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2 ...
- 许式伟看 Facebook 发币(上): 区块链, 比特币与 Libra 币
你好,我是七牛云许式伟. Facebook(脸书)于6月18日发布了其加密数字货币项目白皮书.该数字货币被命名为 Libra(天秤座),象征着平衡与公正.此前,BBC 报道说这个数字货币叫 Globa ...
- iOS-跨界面传值和跨应用传值
跨界面传值 从一个界面将一个结果值传到另一个界面,这个是我们在开发过程中非常常见的一个问题.传值本身并不是一个太复杂的问题,在此主要简述一下常用的传值方法. 我们传值常用的方法主要有四种: 1.属性传 ...
- TypeToken获取运行时泛型类型
最近正好使用到了Guava的TypeToken来获取泛型的类型信息 比如,泛型父类需要获取其子类定义的泛型类型时: public abstract class GenericClazz<V> ...