快速傅里叶变换详解(FFT)
自己也看了几篇博客,但是对我这种不擅长推导小白来说还是有一点困难,所以自己也写一篇博客也为像我一样的小白提供思路。以下内容包含各种LaTeX渲染,如果哪里有错误欢迎大家评论留言,或者添加本人qq:1403482164(无事勿扰)
一、FFT的应用场景
\(A(x) \text{=} a_0 \text{+} a_1x+a_2x^2+……+a_nx^n\)
\(B(x) \text{=} b_0 \text{+} b_1x+b_2x^2+……+b_mx^m\)
\(C(x) \text{=} A(x)\times B(x)\) 求解\(C\)的各项系数
二、前置知识
1. 复数
复数的表示形式:\(a+bi = r(cos\theta + isin\theta) = re^{\theta i}\),前两种高一课程应该都学过,最后请参考欧拉公式
复数在平面直角坐标系上的表示:
主要参考是复数的第一,二种表示形式,实部(\(a / rcos\theta\))为横坐标,虚部(\(b / risin\theta\))为纵坐标,也可以看成是半径为\(r\)的一个圆上的某一个点,圆上的点就可以表示出长度为\(r\)的所有复数
2. 单位根
\(x = 1\),\(x\)有一个解为1,在平面直角坐标系中对应\((1,0)\)
\(x^2 = 1\),\(x\)有两个解为\(1,\text{-}1\),在平面直角坐标系中对应\((1,0)\)和\((\text{-}1,0)\)
\(x^3 = 1\),\(x\)有三个解为\(1,\dfrac{\text{-}1\text{+}\sqrt{3}i}{2},\dfrac{\text{-}1\text{-}\sqrt{3}i}{2}\),在平面直角坐标系中对应\((1,0),(\text{-}\frac{1}{2},\frac{\sqrt{3}}{2}),(\text{-}\frac{1}{2},\text{-}\frac{\sqrt{3}}{2})\)
……
依次类推当\(w^k = 1\)时,\(w\)有\(k\)个解,且这些解在平面直角坐标系中表示出来后把半径为1的圆平分成\(k\)等份,这样的解叫做单位根,其中\(w_{n}^{k}\)表示\(w^n = 1\)的第\(k\)个解
- 单位根的性质
根据复数的表示形式\(w_{n}^{k} = cos\theta \text{+}isin\theta = re^{\theta i}\),其中\(\theta = \frac{2\pi k}{n}\)
通过单位根的形式变化和三角函数计算,可以推出他的一些性质:
\({(w_{n}^{k})}^2 = w_{n}^{2k} = w_{\frac{n}{2}}^{k}\)
\(w_{2n}^{2k} = w_{n}^{k}\)
\(w_{n}^{\frac{n}{2}+k} = \text{-}w_{n}^{k}\)
3. 矩阵乘法
给出\(A(x) = a_0+a_1x+……a_nx^n\)
\(A = \begin{bmatrix}{x_1}^0&{x_1}^1&\cdots&{x_1}^n\\{x_2}^0&{x_2}^1&\cdots&{x_2}^n\\\vdots&\vdots&\vdots&\vdots\\{x_n}^0&{x_n}^1&\cdots&{x_n}^n\end{bmatrix},B = \begin{bmatrix}a_0\\a_1\\\vdots\\a_n\end{bmatrix},C = \begin{bmatrix}A(x_1)\\A(x_2)\\\vdots\\A(x_n)\end{bmatrix}\)
三个矩阵的关系显而易见:\(A\times B = C\),已知\(A,B\)的时候自然可求出\(C\),如果已知\(A,C\)如何求\(B\)呢?
我们引入了两个新的名词:单位矩阵和逆矩阵
单位矩阵\(I\):对角线为1,其余全为0,且满足\(A\times I = A\)
逆矩阵:若\(A\times A^{-1} = I\)则称\(A^{-1}\)为\(A\)的逆矩阵
在等式两边同时乘\(A^{-1}\)变成:\(A\times A^{-1}\times B = C\times A^{-1} \Rightarrow I\times B = C\times A^{-1} \Rightarrow B = C\times A^{-1}\)
这样我们就可以求解\(B\)矩阵了。
4. 函数的表示方法:
系数法:已知所有项的系数当然可以确定一个函数
点值法:\(n\)项的函数,在平面直角坐标系中找\(n+1\)个点就可以确定这个函数
具体证明就不给了,毕竟oi大多数时候还是考感性理解的
三、FFT
讲了个这么多,终于把前置知识处理完毕了,接下来就是正菜FFT了,不同于其他博客,可能我不会引用专业名词像DFT等等……但是精华都是一样的,相信大家都有超前的思考能力,发现某个问题不知道如何处理,耐心看下去,也许会发现不一样的精彩。
1. 为什么用FFT,他如何优化问题
首先还是这个式子:\(A(x) = a_0+a_1x^2+……a_nx^n\)
当我给出一个\(x\),朴素求解的时间复杂度是多少呢? 循环叠加\(x\),\(O(n)\)求解,而我们需要\(n+1\)个点,那么确定一个函数的复杂度就是\(O(n^2)\)的
显然不够优秀是吧,而他的困难就在于对每个\(x\)求\(A(x)\)的过程,所以我们想借助某种方法把这个过程优化到\(O(logn)\),就发明了FFT
2. 具体流程
1. 先想办法把式子处理一下:
奇偶分离(方便推导就假设\(n\)为偶数,实际写代码的时候,往后加0系数就可以了):
\(A_0(x) = a_0+a_2x^2+……a_nx^n,A_1(x) = a_1x+a_3x^3+……+a_{n+1}x^{n+1}\),其中\(a_{n+1} = 0\),为了让两个函数的项数相同
此时如果\(A_0,A_1,A\)三者的形式相同就好了,这样就可以统一求解了
我们可以发现如果我从\(A_1(x)\)中提取一个\(x\)出来,那么他们之间的形式是一样的,所以我让\(xA_1(x) = a_1x+a_3x^3+a_{n+1}x^{n+1}\),那么\(A_1(x) = a_1 +a_3x^2+……+a_{n+1}x^n\)
那么现在\(A_0\)和\(A_1\)的形式相同,我们还需要把他们两个和\(A\)统一形式,很简单,我们把\(x\)替换成\(y = x^2\),此时\(A_0(y) = a_0+a_2y+a_4y^2+……+a_ny^{\frac{n}{2}}\),\(A_1(y) = a_1+a_3y+……+a_{n+1}y^{\frac{n}{2}}\)
到现在为止,我们已经把\(A_0\)和\(A_1\)从\(A\)中提取出来了,又要怎么合并起来呢?
其实这里面还有一个隐藏的关系:\(A(x) = A_0(x^2)+xA_1(x^2)\),往上面的式子里代一下,就可以得知了,这样分成两部分算,时间缩短了一半
2. 求解点值
巧妙的地方来了,除了式子的处理,我代入的点也十分的有讲究。大家也能猜到,前置知识讲的单位根肯定不是白讲的吧。
把单位根\(w_{n}^{k}\)代入可得:
当\(k \leq \frac{n}{2}\)时,\(A(w_{n}^{k}) = A_0({w_{n}^{k}}^2)+w_{n}^{k}A_1({w_{n}^{k}}^2) = A_0(w_{\frac{n}{2}}^{k})+w_{n}^{k}A_1(w_{\frac{n}{2}}^{k})\)
当\(k \text{>} \frac{n}{2}\)时,把\(k+\frac{n}{2}\)代入,\(A(w_{n}^{k+\frac{n}{2}}) = A_0({w_{n}^{k+\frac{n}{2}}}^2)+w_{n}^{k+\frac{n}{2}}A_1({w_{n}^{k+\frac{n}{2}}}^2) = A_0(w_{\frac{n}{2}}^{k})-w_{n}^{k}A_1(w_{\frac{n}{2}}^{k})\)
我们发现两个情况只有加减号不同,所以在求解前一半的时候,后一半同时可以求解,时间又缩小了一半,\(O(logn)\)的复杂度就出来了
3. 求解最终函数
算法进行到了最最最最后一步了,现在我们通过上述的一系列计算,已知了\(A(x)\)和\(B(x)\)的\(n+m+1\)个点,\(C(x) = A(x)*B(x)\),自然就得出了\(C(x)\)的\(n+m+1\)个点,这就要考察到我们前置内容中的矩阵乘法部分了。
我们已知\(A = \begin{bmatrix}{w_n^1}^0&{w_n^1}^1&\cdots&{w_n^1}^n\\{w_n^2}^0&{w_n^2}^1&\cdots&{w_n^2}^n\\\vdots&\vdots&\vdots&\vdots\\{w_n^n}^0&{w_n^n}^1&\cdots&{w_n^n}^n\end{bmatrix},C = \begin{bmatrix}C(w_n^1)\\C(w_n^2)\\\vdots\\C(w_n^n)\end{bmatrix}\),来求解\(B\)矩阵
所以问题转化成求解\(A^{-1}\)矩阵,给出一个结论,\(A^{-1}\)矩阵就等于把\(w\)的所有上角标取负再乘\(\frac{1}{n}\)
以上就是FFT的全部流程,剩下还有动态规划优化,敬请期待下回详解……
四、递归版代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
const double pi = acos(-1.0);const int maxn = 1e7+10;
int read(){
int x = 1,a = 0;char ch = getchar();
while (ch < '0'||ch > '9'){if (ch == '-') x = -1;ch = getchar();}
while (ch >= '0'&&ch <= '9'){a = a*10+ch-'0';ch = getchar();}
return x*a;
}
struct node{
double x,y;
}a[maxn], b[maxn];
node operator + (node a,node b){return node{a.x+b.x,a.y+b.y};}
node operator - (node a,node b){return node{a.x-b.x,a.y-b.y};}
node operator * (node a,node b){return node{a.x*b.x-a.y*b.y,a.y*b.x+a.x*b.y};}
int n,m;
void fft(int len, node *a, int op){
if (len == 1) return;
node a0[(len>>1)+3],a1[(len>>1)+3];
for (int i = 0;i <= len;i += 2) a0[i>>1] = a[i],a1[i>>1] = a[i+1];
fft(len>>1,a0,op);fft(len>>1,a1,op);
node wn = node{cos(2*pi/len),op*sin(2*pi/len)},w0 = node{1,0};
for(int i = 0;i < (len >> 1);i++,w0 = w0*wn){
a[i] = a0[i]+w0*a1[i];
a[i+ (len>>1)] = a0[i]- w0*a1[i];
}
}
int main(){
//freopen("in.in","r",stdin);
//freopen("out.out","w",stdout);
n = read(),m = read();
for (int i = 0;i <= n;i++) a[i].x = read();
for (int i = 0;i <= m;i++) b[i].x = read();
int len = 1;
while (len <= n+m) len <<= 1;
fft(len,a,1);fft(len,b,1);
for (int i = 0;i <= len;i++) a[i] = a[i]*b[i];
fft(len,a,-1);
for (int i = 0;i <= n+m;i++) printf("%.0lf ",a[i].x/len);
return 0;
}
被WC摧残的第一天,回来更新了……
首先来模拟一下递归的过程(盗用wiki):
规律: 其实就是原来的序列,每个数用二进制表示,然后把二进制翻转对称一下,就是最终那个位置的下标。比如\(x\)是001,翻转是100,也就是4,即\(x\)最后的位置。我们称这个变换为位逆序置换(bit-reversal permutation,国内也称蝴蝶变换)。(把二进制写出来就很好发现了,就是想不到写二进制呀)
进而我们还可以发现,每一层对应的两个数都是由他下面一层对应的两个数更新而来,这样问题就转化成了求最后一层数的顺序了,也就是求每个数的位逆序……
因为每个数都要求一遍,所以想能到dp转移
状态定义:\(dp_i\)表示\(i\)的位逆序
状态转移:\(dp_i = \frac{dp_{i/2}}{2} \text{+} (i\text{%}2)\times 2^{l-1}\)(自己模拟一下过程,还是挺显而易见的)
五、非递归版代码(话说luogu卡精度来着)
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
const double pi = acos(-1.0);const int maxn = 1e7+10;
int read(){
int x = 1,a = 0;char ch = getchar();
while (ch < '0'||ch > '9'){if (ch == '-') x = -1;ch = getchar();}
while (ch >= '0'&&ch <= '9'){a = a*10+ch-'0';ch = getchar();}
return x*a;
}
struct node{
double x,y;
}a[maxn], b[maxn],a0[maxn],a1[maxn];
node operator + (node a,node b){node x = {a.x+b.x,a.y+b.y};return x;}
node operator - (node a,node b){node x = {a.x-b.x,a.y-b.y};return x;}
node operator * (node a,node b){node x = {a.x*b.x-a.y*b.y,a.y*b.x+a.x*b.y};return x;}
int n,m,dp[maxn];
double coss[maxn],sinn[maxn];
void fft(int len,node *a,int op){
for (int i = 0;i <= len;i++){
if (i < dp[i]) swap(a[i],a[dp[i]]);
}
for (int l = 1;l < len;l<<=1){
node wn = {coss[l],op*sinn[l]};
for (int i = 0;i < len;i+=(l << 1)){
node w0 = {1,0};
for (int j = 0;j < l;j++,w0 = w0*wn){
node x = a[i+j],y = w0*a[i+j+l];
a[i+j] = x+y,a[i+j+l] = x-y;
}
}
}
}
int main(){
//freopen("in.in","r",stdin);
//freopen("out.out","w",stdout);
n = read(),m = read();
for (int i = 0;i <= n;i++) a[i].x = read();
for (int i = 0;i <= m;i++) b[i].x = read();
int len = 1,num = 0;
while (len <= n+m) len <<= 1,num++;
for (int i = 0;i <= len;i++) dp[i] = (dp[i>>1]>>1)|((i&1)<<(num-1));
for (int i = 1;i <= len;i <<= 1) coss[i] = cos(pi/i),sinn[i] = sin(pi/i);
fft(len,a,1);fft(len,b,1);
for (int i = 0;i <= len;i++) a[i] = a[i]*b[i];
fft(len,a,-1);
for (int i = 0;i <= n+m;i++) printf("%d ",(int)(a[i].x/len+0.5));
return 0;
}
快速傅里叶变换详解(FFT)的更多相关文章
- Redis快速入门详解
Redis入门详解 Redis简介 Redis安装 Redis配置 Redis数据类型 Redis功能 持久化 主从复制 事务支持 发布订阅 管道 虚拟内存 Redis性能 Redis部署 Redis ...
- numpy教程:快速傅里叶变换模块numpy.fft
http://blog.csdn.net/pipisorry/article/details/51050297 快速傅里叶变换 NumPy中,fft模块提供了快速傅里叶变换的功能.在这个模块中,许多函 ...
- 洛谷 P4910 帕秋莉的手环 矩阵乘法+快速幂详解
矩阵快速幂解法: 这是一个类似斐波那契数列的矩乘快速幂,所以推荐大家先做一下下列题目:(会了,差不多就是多倍经验题了) 注:如果你不会矩阵乘法,可以了解一下P3390的题解 P1939 [模板]矩阵加 ...
- 跨浏览器复制神器 ZeroClipboard 2.x快速入门详解
有些时候,我们希望让用户在网页上完成某个操作就能自动将指定的内容复制到用户计算机的剪贴板中.但是出于安全原因,大多数现代浏览器都未提供通用的剪贴板复制接口(或即便有,也默认被禁用).只有IE浏览器可以 ...
- iOS中 CoreGraphics快速绘图(详解) 韩俊强的博客
每日更新关注:http://weibo.com/hanjunqiang 新浪微博 第一步:先科普一下基础知识: Core Graphics是基于C的API,可以用于一切绘图操作 Core Graph ...
- shiro 快速入门详解。
package com.aaa.lee.shiro; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; i ...
- 「学习笔记」FFT 快速傅里叶变换
目录 「学习笔记」FFT 快速傅里叶变换 啥是 FFT 呀?它可以干什么? 必备芝士 点值表示 复数 傅立叶正变换 傅里叶逆变换 FFT 的代码实现 还会有的 NTT 和三模数 NTT... 「学习笔 ...
- 快速傅里叶变换(FFT)算法【详解】
快速傅里叶变换(Fast Fourier Transform)是信号处理与数据分析领域里最重要的算法之一.我打开一本老旧的算法书,欣赏了JW Cooley 和 John Tukey 在1965年的文章 ...
- 快速傅里叶变换(FFT)详解
本文只讨论FFT在信息学奥赛中的应用 文中内容均为个人理解,如有错误请指出,不胜感激 前言 先解释几个比较容易混淆的缩写吧 DFT:离散傅里叶变换—>$O(n^2)$计算多项式乘法 FFT:快速 ...
- FFT(快速傅里叶变换)算法详解
多项式的点值表示(Point Value Representation) 设多项式的系数表示(Coefficient Representation): \[ \begin{align*} \mathr ...
随机推荐
- python爬取丁香园疫情数据
毕设需求了就是说 导师要做关于时间线的- -看发展趋势 不得不今天又现学现卖 首先 创建一个python文件 python.file 引入一点资源 # 发送请求 import requests # 页 ...
- The Missing Semester - 第二讲 学习笔记
第二讲 Shell 工具和脚本 课程视频地址: https://www.bilibili.com/video/BV1Vv411v7FR 本机学习使用平台:虚拟机ubuntu18.04.6 主题一:Sh ...
- uboot启动过程 1
打开 u-boot.lds OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-li ...
- 六:spring Cloud
六Spring Cloud 回顾之前的: javaSE 数据库 前端 Servlet HTTP Mybatis Spring SpringMVC SpringBoot Dubbo.Zookeeper. ...
- GoAccess实现请求监
GoAccess实现请求监控 简介 GoAccess是一款开源的实时web日志分析器和交互式查看器,用于可视化查看HTTP统计信息,可以系统的终端上运行,也可以通过浏览器运行: 本文通过使用GoAcc ...
- 【GDKOI 2021提高组DAY2】抄写
\(\text{Solution}\) \(dp\) 翻折就只需预处理回文中心 \(Manacher\) 预处理即可 \(Code\) #include<cstdio> #include& ...
- 【雅礼联考DAY02】Revolution
#include<cstdio> #include<queue> using namespace std; const int N = 210 , M = 210000 , I ...
- 轻量级CI/CD发布部署环境搭建及使用_02_docker安装jenkins
轻量级CI/CD发布部署环境搭建及使用_02_docker安装jenkins 授人以鱼不如授人以渔,如果说的别人都没明白,说明自己实际也不是太明白 1,搜索jenkins docker searc ...
- 在线设计器 DesignO 的分析
需求分析 现有POD网站的在线编辑器不是很好用. 可配置性不强,素材无法在后台实现管理 可扩展性不强,无法应用于多个行业,比如包装.服装 产品分析 官方网站:https://www.designnbu ...
- LeetCode-23 合并K个升序链表
来源:力扣(LeetCode)链接:https://leetcode-cn.com/problems/merge-k-sorted-lists 题目描述 给你一个链表数组,每个链表都已经按升序排列. ...