前言

\(\text{FFT}\)(快速傅里叶变换)是 \(O(n\log n)\) 解决多项式乘法的一个算法,\(\text{NTT}\)(快速数论变换)则是在模域下的,而 \(\text{MTT}\)(毛神仙对\(\text{FFT}\)的精度优化算法)可以针对任意模数。本文主要讲解这三种算法,具体的应用还请参考我博客内的题解。

正文

FFT-快速傅里叶变换

学习这个算法可以借助《算法导论》,当然算导上的东西需要耐心才能啃下来。这里只是概括一下算导上的介绍,并加入一些个人的见解。下面逐步介绍这个算法。

复数

如果学过的话可以跳过。实数可以一一对应数轴上的点,那么复数就可以一一对应平面直角坐标系上的点。对应 \(x\) 轴上的点的就是我们熟悉的实数,而外面的就是虚数。其中 \((0,1)\) 这个点对应的数记作 \(i\) ,即 \(\sqrt{-1}\),它表示虚数单位。复数可以表示成 \(a+ib\) 的形式,其中 \(a,b\) 为实数。

极坐标表示法下的乘法

\((a,\alpha)\cdot(b,\beta)=(ab,\alpha+\beta)\)

证明如下:

\[\begin{align}{}
&(a,\alpha)\cdot(b,\beta) \notag\\
=&ab(\cos\alpha+i\sin\alpha)(\cos\beta+i\sin\beta)\notag\\
=&ab[\cos\alpha\cos\beta+i(\sin\alpha\cos\beta+\cos\alpha\sin\beta)-\sin\alpha\sin\beta]\notag\\
=&ab[\cos(\alpha+\beta))+i\sin(\alpha+\beta)]\notag\\
=&(ab,\alpha+\beta)\notag
\end{align}
\]

代数表示法下的乘法

\((a+ib)\cdot(c+id)=ac-bd+i(ad+bc)\)

无需证明,肉眼化简。

单位复数根

在单位圆上,我们用 \(\omega_{n}^k\) 表示将单位圆 \(n\) 等分,取其第 \(k\) 条线对应的单位复数。其中 \(\omega_n^0=1\) ,逆时针方向编号,如图所示:

单位复根有一些重要的性质。

消去引理

\(\omega_{dn}^{dk}=\omega_{n}^k\) 其中 \(n,k\geq 0,d>0\)

折半引理

\((\omega_n^{k+n/2})^2=(\omega_n^k)^2\) 其中\(n\geq0,k\geq 0\)

如果借助向量去理解的话,理解起来非常方便。

多项式

一个形如 \(\displaystyle A(x)=\sum_{i=0}^{n-1}a_ix^i\) 的式子。

系数表示

直接列出 \(A(x)\) 的各项系数。这种表示方法可以 \(O(n)\) 的实现多项式加法,但多项式乘法却需要 \(O(n^2)\)

点值表示

通过带入若干个特值确定,显然,一个最高次为 \(n-1\) 的多项式需要 \(n\) 的特殊值便唯一确定。这种表示方法可以 \(O(n)\) 的加和乘,但是要转化成系数表示才能体现出它作为多项式的价值。

DFT

对于一个列向量 \(a=(a_0,a_1,\cdots,a_{n-1})\) ,以它为系数的多项式 \(A(x)=\displaystyle\sum_{j=0}^{n-1}a_jx^j\)

若有一个列向量 \(y=(y_0,y_1,\cdots,y_{n-1})\) 满足 \(y_k=A(\omega_n^k)\) ,则\(y=\text{DFT}_n(a)\)

\(\text{DFT}\) 的全称为离散傅里叶变换,是将多项式的系数表达化作点值表达的一个变换。

同理 \(a=\text{DFT}_n^{-1}(y)\) ,\(\text{DFT}^{-1}\) 就是逆离散傅里叶变换,也称 \(\text{IDFT}\),我们尝试写出 \(\text{DFT}^{-1}\) 的表达式。

写出 \(y\) 与 \(a\) 的关系

\[y_k=\sum_{j=0}^{n-1}a_j\omega^{kj}
\]

然后我们可以矩阵乘积 \(y=V_na\) 的形式表示向量 \(a\) 到向量 \(y\) 的变换。\(V_n\) 为由 \(\omega_n\) 各项指数构成的范德蒙德矩阵。

\[\begin{pmatrix}
y_0\\
y_1\\
y_2\\
y_3\\
\vdots\\
y_{n-1}\\
\end{pmatrix}
=
\begin{pmatrix}
\omega^0 & \omega^0 &\omega^0 & \omega^0 & \cdots & \omega^0 \\
\omega^0 & \omega^1 &\omega^2 & \omega^3 & \cdots & \omega^{(n-1)}\\
\omega^0 & \omega^2 &\omega^4 & \omega^6 & \cdots & \omega^{2(n-1)} \\
\omega^0 & \omega^3 &\omega^6 & \omega^9 & \cdots & \omega^{3(n-1)} \\
\vdots & \vdots & \vdots & \vdots & \ddots &\vdots\\
\omega^{0} & \omega^{1(n-1)} &\omega^{2(n-1)} & \omega^{3(n-1)} & \cdots & \omega^{(n-1)(n-1)} \\
\end{pmatrix}
\begin{pmatrix}
a_0\\
a_1\\
a_2\\
a_3\\
\vdots\\
a_{n-1}\\
\end{pmatrix}
\]

那我们现在要求的就是 \(V_n^{-1}\) 的矩阵,即 \(V_n\) 的逆矩阵。

有如下定理:

对于 \(j,k\in[0,n)\) ,\(V_n^{-1}\) \((j,k)\) 处的元素为 \(\omega_n^{-jk}/n\)

证明如下

\[\begin{array}{}
[V_nV_n^{-1}]_{jj'}&=\displaystyle\sum_{k=0}\omega_n^{jk}\omega^{-kj'}/n\\
&=\displaystyle{1\over n}\sum_{k=0}\omega_n^{k(j-j')}
\end{array}
\]

显然,当 \(j=j'\) 时,\([V_nV_n^{-1}]_{jj'}\) 的值为 \(1\) ,否则为 \(0\) ,那么 \([V_nV_n^{-1}]\) 是一个行列数为 \(n\) 的单位矩阵,即得证 \(V^{-1}\) 为 \(V\) 的逆矩阵。

那么在作 \(\text{IDFT}\) 的时候,只需将单位根换成 \({\omega_n^{-1}}\) ,最后系数再除以 \(n\) 即可。

当然,直接变换是 \(O(n^2)\) 的。我们考虑用分治的思想进行变换。

FFT

首先观察多项式 \(A(x)\) ,我们将指数分奇偶两类。偶数项以 \(\{a_0,a_2,...,a_{n-2}\}\) 构造一个新的多项式 \(\displaystyle A^{[0]}(x)=\sum_{j=0}^{n/2-1}a_{2j}x^j\),奇数项同理为 \(\displaystyle A^{[1]}(x)=\sum_{j=0}^{n/2-1}a_{2j+1}x^j\)。

那么显然有

\[A(x)=A^{[0]}(x^2)+xA^{[1]}(x^2)
\]

我们把 \(\omega_n^k\) 代入得到

\[A(\omega_n^k)=A^{[0]}(\omega_n^{2k})+\omega_n^kA^{[1]}(\omega_n^{2k})
\]

利用消去引理得到

\[A(\omega_n^k)=A^{[0]}(\omega_{n/2}^k)+\omega_n^kA^{[1]}(\omega_{n/2}^k)
\]

那么将 \(A^{[0]},A^{[1]}\) 的系数向量 \(a^{[0]},a^{[1]}\) 进行一次 \(\text{DFT}\) ,分别得到 \(y^{[0]},y^{[1]}\) 。

\[y^{[0]}_k=A^{[0]}(\omega_{n/2}^k)\\
y^{[1]}_k=A^{[1]}(\omega_{n/2}^k)
\]

只要令 \(k<n/2\) ,将 \(k\geq n/2\) 的部分用折半引理即可。

\[A(\omega_n^k)=A^{[0]}(\omega_{n/2}^k)+\omega_n^kA^{[1]}(\omega_{n/2}^k)\\
A(\omega_n^{k+n/2})=A^{[0]}(\omega_{n/2}^k)-\omega_n^kA^{[1]}(\omega_{n/2}^k)
\]

推导不难,注意将在单位圆上的旋转借用平面向量来理解。

用 \(y\) 代入,最终的表达式为

\[y_k=y^{[0]}_k+\omega_n^ky_k^{[1]}\\
y_{k+n/2}=y_k^{[0]}-\omega_n^ky_k^{[1]}
\]

这样就可以分治求解了。

更高效的FFT

事实上 \(\text{FFT}\) 可以迭代求解。先观察一下递归求解的过程,如图所示。

然后用人类智慧观察,发现 \(a_i\) 在底层是在的位置为 \(i\) 的二进制位翻转。

发现只需要枚举区间长度,扫整个序列,就可以进行对区间进行合并。观察递归求解的式子

\[y_k=y^{[0]}_k+\omega_n^ky_k^{[1]}\\
y_{k+n/2}=y_k^{[0]}-\omega_n^ky_k^{[1]}
\]

它的流程可以用上图来表示,上面操作叫作蝴蝶操作,其实和递归求解的流程相似。具体还是看代码,码风还是清晰的。

struct Complex
{
double x,y;
Complex operator +(const Complex &_){return (Complex){x+_.x,y+_.y};}
Complex operator -(const Complex &_){return (Complex){x-_.x,y-_.y};}
Complex operator *(const Complex &_){return (Complex){x*_.x-y*_.y,x*_.y+y*_.x};}
Complex operator /(const int &_){return (Complex){x/_,y/_};}
};
namespace _Polynomial
{
Complex A[N<<1],B[N<<1];
Complex w[N<<1];int r[N<<1];
void DFT(Complex *a,int op,int n)
{
FOR(i,0,n-1)if(i<r[i])swap(a[i],a[r[i]]); //位翻转
for(int i=2;i<=n;i<<=1) //合并出一个长i的区间
for(int j=0;j<n;j+=i) //区间开头的位置
for(int k=0;k<i/2;k++) //蝴蝶操作
{
Complex u=a[j+k],t=w[op==1?n/i*k:n-n/i*k]*a[j+k+i/2];
a[j+k]=u+t,a[j+k+i/2]=u-t;
}
if(op==-1)FOR(i,0,n-1)a[i]=a[i]/n;
}
void multiply(const int *a,const int *b,int *c,int n1,int n2)
{
int n=1;
while(n<n1+n2-1)n<<=1;
FOR(i,0,n1-1)A[i].x=a[i],A[i].y=0;
FOR(i,0,n2-1)B[i].x=b[i],B[i].y=0;
FOR(i,n1,n-1)A[i].x=A[i].y=0;
FOR(i,n2,n-1)B[i].x=B[i].y=0;
FOR(i,0,n-1)r[i]=(r[i>>1]>>1)|((i&1)*(n>>1));
FOR(i,0,n)w[i]=(Complex){cos(2*PI*i/n),sin(2*PI*i/n)}; DFT(A,1,n),DFT(B,1,n);
FOR(i,0,n-1)A[i]=A[i]*B[i];
DFT(A,-1,n);
FOR(i,0,n1+n2-2)c[i]=A[i].x+0.5;
}
};

显而易见,由于 \(\text{double}\) 的存在,精度多多少少会被卡一点。而具体的题目经常往往会给一个特殊的模数,这种时候就要用到接下来介绍的算法了。

NTT-快速数论变换

待补充。。。

浅谈FFT、NTT和MTT的更多相关文章

  1. 浅谈FFT&NTT

    复数及单位根 复数的定义大概就是:\(i^2=-1\),其中\(i\)就是虚数单位. 那么,在复数意义下,对于方程: \[ x^n=1 \] 就必定有\(n\)个解,这\(n\)个解的分布一定是在复平 ...

  2. 浅谈FFT(快速傅里叶变换)

    前言 啊摸鱼真爽哈哈哈哈哈哈 这个假期努力多更几篇( 理解本算法需对一些< 常 用 >数学概念比较清楚,如复数.虚数.三角函数等(不会的自己查去(其实就是懒得写了(¬︿̫̿¬☆) 整理了一 ...

  3. 浅谈FFT(快速傅里叶变换)

    本文主要简单写写自己在算法竞赛中学习FFT的经历以及一些自己的理解和想法. FFT的介绍以及入门就不赘述了,网上有许多相关的资料,入门的话推荐这篇博客:FFT(最详细最通俗的入门手册),里面介绍得很详 ...

  4. 浅谈FFT(快速博立叶变换)&学习笔记

    0XFF---FFT是啥? FFT是一种DFT的高效算法,称为快速傅立叶变换(fast Fourier transform),它根据离散傅氏变换的奇.偶.虚.实等 特性,对离散傅立叶变换的算法进行改进 ...

  5. 浅谈范德蒙德(Vandermonde)方阵的逆矩阵的求法以及快速傅里叶变换(FFT)中IDFT的原理

    浅谈范德蒙德(Vandermonde)方阵的逆矩阵与拉格朗日(Lagrange)插值的关系以及快速傅里叶变换(FFT)中IDFT的原理 标签: 行列式 矩阵 线性代数 FFT 拉格朗日插值 只要稍微看 ...

  6. FFT/NTT/MTT学习笔记

    FFT/NTT/MTT Tags:数学 作业部落 评论地址 前言 这是网上的优秀博客 并不建议初学者看我的博客,因为我也不是很了解FFT的具体原理 一.概述 两个多项式相乘,不用\(N^2\),通过\ ...

  7. FFT&NTT总结

    FFT&NTT总结 一些概念 \(DFT:\)离散傅里叶变换\(\rightarrow O(n^2)\)计算多项式卷积 \(FFT:\)快速傅里叶变换\(\rightarrow O(nlogn ...

  8. FFT/NTT复习笔记&多项式&生成函数学习笔记Ⅰ

    众所周知,tzc 在 2019 年(12 月 31 日)就第一次开始接触多项式相关算法,可到 2021 年(1 月 1 日)才开始写这篇 blog. 感觉自己开了个大坑( 多项式 多项式乘法 好吧这个 ...

  9. 浅谈 Fragment 生命周期

    版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Fragment 文中如有纰漏,欢迎大家留言指出. Fragment 是在 Android 3.0 中 ...

随机推荐

  1. Java基础(basis)-----异常与错误处理

    1.编译型异常和运行时异常       编译时异常是指程序正确 而由外界条件不满足而产生的异常 java 中要求必须去捕捉住这类异常 不然无法通过编译 运行时异常是指程序存在着bug 如空指针异常 数 ...

  2. CSS背景与边框属性-----box-shadow

    box-shadow:none | <shadow> [ , <shadow> ]*   <shadow> = inset? && <leng ...

  3. EasyUI创建DataGrid及冻结列的两种方式

       第一种方式:通过HTML标签创建数据表格控件 <table class="easyui-datagrid" title="基本数据表格" style ...

  4. Jersey入门——对Json的支持

    Jersey rest接口对POJO的支持如下: package com.coshaho.learn.jersey; import java.net.URI; import javax.ws.rs.C ...

  5. idea下导入Tomcat源码

    对于web开发者来说,如果明白了tomcat那对于开发还是后面的学习都是有很大益处的,但在网上看了很多的文章,总是没弄好,经历了很久才弄好了,写个文章记录下,希望也能帮助到其他人.下载Tomcat源码 ...

  6. Python+OpenCV图像处理(四)—— 色彩空间

    一.色彩空间的转换 代码如下: #色彩空间转换 import cv2 as cv def color_space_demo(img): gray = cv.cvtColor(img, cv.COLOR ...

  7. 安装ubuntu18.04.1

    下载ubuntu:https://www.ubuntu.com/download/desktop 在虚拟机创建好ubuntu18.04.1后无法启动(选择的是linux,ubuntu64位),提示:此 ...

  8. centos系统swap设置 查看swap分区的方法

    交换分区swap,意思是“交换”.“实物交易”,它的功能就是在内存不够的情况下,操作系统先把内存中暂时不用的数据,存到硬盘的交换空间,腾出内存来让别的程序运行,和Windows的虚拟内存(pagefi ...

  9. Vue小案例 之 商品管理------批量删除与商品数量的调整

    通过索引进行删除,进行测试,是否获取其索引: 测试效果: 测试代码,在vue中定义一个空的数组,以便后面进行数据的绑定: data:{ imgUrl:'../res/images/', imgName ...

  10. CSM与UEFI

    最近公司产品部购置一批新电脑,但是预装的win10不能保证兼容老平台软件,于是安装win7系统的任务就落到了我的手中. 观察参数,是8代的U,产品说运维说无能为力,装不了win7.我在网上搜了一下,是 ...