快速傅里叶变换(FFT)基础
本文是对 FFT 和 NTT 原理及实现的介绍,包含所有必要的证明. 阅读本文需要具备一点基本的代数知识.
给定
算法介绍
设
注意到(提升注意力),只要选取
是质数 和 的原根.
在下文中,记
DFT
DFT 的实现是基于倍增的(如果多项式次数不是
对于
将
假设我们已经递归地求出
时间代价与最经典的分治法一样,有
IDFT
前排提示:下文的
含义与上文不同,表示某个 的幂次, 是一个 阶循环群的生成元. 矩阵单位 的第 行第 列(下标从 开始)为 ,其他位置为 . 容易验证, ;当 时 .
多项式多点求值是对系数向量的线性变换,变换后的结果是点值向量. 这个变换矩阵称作范德蒙德矩阵:
DFT 可以看作给系数向量左乘一个特殊的范德蒙德矩阵
于是,IDFT 相当于给点值向量左乘逆矩阵. 可以用拉格朗日插值求得这个逆矩阵,我们这里直接给出逆矩阵的形式(证明见下文):
其中
注:严谨地讲,在一般的环
上,实际上是乘以 的逆,其中 为乘法单位元. 其实,因为 总是 的整数幂,只要能求 的逆就行.
我们还注意到(提升注意力)上面的矩阵恰好等于
(这很容易验证,只须注意到同一行上第
最后我们做一个简单的乘法,验证它确实是逆矩阵. 需要预先准备一个特殊的等比数列求和
证明是朴素的,只须注意到
于是
后排提示:这里记号
的含义恢复,现在多项式 和 的次数小于 .
递推实现
DFT 是递归的过程,但我们可以自底向下地向上合并,实现非递归且原地(直接把系数数组改成点值数组)的 DFT.
首先对于长度为
为了做到完全原地,还要注意一个细节. 当合并两个
三次变两次优化
求多项式乘法总共要做三次 DFT. 对于
三次变两次优化后的 FFT 比 NTT 稍快.
样例代码
template<bool INV=false, typename T>
void DFT(T F[], int n){ // precondition: n==2^k
for(int i=0; i<n; i++) rev[i]=rev[i>>1]>>1|(i&1)<<(n-1);
for(int i=0; i<n; i++) if(i<rev[i]) swap(F[i], F[rev[i]]);
for(int len=2; len<=n; len<<=1){
const T w0=exp(complex<double>(0, 2*M_PI/len))/* g^(mod-1)/len */;
const int mid=len>>1;
for(int i=0; i<n; i+=len){
T w(1);
for(int j=i; j<i+mid; j++){
const T x=F[j], y=w*F[j+mid];
F[j]=x+y, F[j+mid]=x-y;
w*=w0;
}
}
}
if(INV){
const auto invn=T(1)/n;
for(int i=0;i<n;i++) F[i]*=invn;
reverse(F+1, F+n);
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?