自己也看了几篇博客,但是对我这种不擅长推导小白来说还是有一点困难,所以自己也写一篇博客也为像我一样的小白提供思路。以下内容包含各种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}\)

通过单位根的形式变化和三角函数计算,可以推出他的一些性质:

  1. \({(w_{n}^{k})}^2 = w_{n}^{2k} = w_{\frac{n}{2}}^{k}\)

  2. \(w_{2n}^{2k} = w_{n}^{k}\)

  3. \(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\)呢?

我们引入了两个新的名词:单位矩阵和逆矩阵

  1. 单位矩阵\(I\):对角线为1,其余全为0,且满足\(A\times I = A\)

  2. 逆矩阵:若\(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. 函数的表示方法:

  1. 系数法:已知所有项的系数当然可以确定一个函数

  2. 点值法:\(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)的更多相关文章

  1. Redis快速入门详解

    Redis入门详解 Redis简介 Redis安装 Redis配置 Redis数据类型 Redis功能 持久化 主从复制 事务支持 发布订阅 管道 虚拟内存 Redis性能 Redis部署 Redis ...

  2. numpy教程:快速傅里叶变换模块numpy.fft

    http://blog.csdn.net/pipisorry/article/details/51050297 快速傅里叶变换 NumPy中,fft模块提供了快速傅里叶变换的功能.在这个模块中,许多函 ...

  3. 洛谷 P4910 帕秋莉的手环 矩阵乘法+快速幂详解

    矩阵快速幂解法: 这是一个类似斐波那契数列的矩乘快速幂,所以推荐大家先做一下下列题目:(会了,差不多就是多倍经验题了) 注:如果你不会矩阵乘法,可以了解一下P3390的题解 P1939 [模板]矩阵加 ...

  4. 跨浏览器复制神器 ZeroClipboard 2.x快速入门详解

    有些时候,我们希望让用户在网页上完成某个操作就能自动将指定的内容复制到用户计算机的剪贴板中.但是出于安全原因,大多数现代浏览器都未提供通用的剪贴板复制接口(或即便有,也默认被禁用).只有IE浏览器可以 ...

  5. iOS中 CoreGraphics快速绘图(详解) 韩俊强的博客

    每日更新关注:http://weibo.com/hanjunqiang  新浪微博 第一步:先科普一下基础知识: Core Graphics是基于C的API,可以用于一切绘图操作 Core Graph ...

  6. shiro 快速入门详解。

    package com.aaa.lee.shiro; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; i ...

  7. 「学习笔记」FFT 快速傅里叶变换

    目录 「学习笔记」FFT 快速傅里叶变换 啥是 FFT 呀?它可以干什么? 必备芝士 点值表示 复数 傅立叶正变换 傅里叶逆变换 FFT 的代码实现 还会有的 NTT 和三模数 NTT... 「学习笔 ...

  8. 快速傅里叶变换(FFT)算法【详解】

    快速傅里叶变换(Fast Fourier Transform)是信号处理与数据分析领域里最重要的算法之一.我打开一本老旧的算法书,欣赏了JW Cooley 和 John Tukey 在1965年的文章 ...

  9. 快速傅里叶变换(FFT)详解

    本文只讨论FFT在信息学奥赛中的应用 文中内容均为个人理解,如有错误请指出,不胜感激 前言 先解释几个比较容易混淆的缩写吧 DFT:离散傅里叶变换—>$O(n^2)$计算多项式乘法 FFT:快速 ...

  10. FFT(快速傅里叶变换)算法详解

    多项式的点值表示(Point Value Representation) 设多项式的系数表示(Coefficient Representation): \[ \begin{align*} \mathr ...

随机推荐

  1. .Net Core后端架构实战【1-项目分层框架设计】

    摘要:基于.NET Core 7.0WebApi后端架构实战[1-项目结构分层设计]  2023/02/05, ASP.NET Core 7.0, VS2022 引言 从实习到现在回想自己已经入行四年 ...

  2. Mybatis Plus (特性、快速入门、日志、CRUD)

    Mybatis plus 可以节省很多的工作,所有的CRUD JPA yk-mapper Mybatis plus 偷懒的 简介: MyBatis-Plus(opens new window)(简称 ...

  3. clicknium-划时代的自动化

    说起UI自动化,selenium一直是不可替代的存在,它的安装量.百度数量已经奠定了它在自动化.爬虫等相关领域的霸主低位,可以说,只要涉及到UI自动化,选selenium就对了. 细数过往,相关的UI ...

  4. vue-seamless-scroll滚动加点赞衔接处数据不同步问题

    VUE使用vue-seamless-scroll自动滚动加点赞,因为有两个overhidden导致点击不同同步dom,在代码中会出现两处vue-seamless-scroll上下悬接,悬接处点赞触发没 ...

  5. Linux:atime、mtime、ctime

    三个时间 Linux 的文件属性中有:访问时间(Access Time).修改时间(Modify Time).改变时间(Change Time): 访问时间(Access Time):对文件进行一次读 ...

  6. .NET周报 【2月第4期 2023-02-25】

    国内文章 .NET微服务系统迁移至.NET6.0的故事 https://www.cnblogs.com/InCerry/p/microservice-migration-net-6.html 本次迁移 ...

  7. 初始化安装后 Nacos 动态路由配置不生效

    一.问题描述 1.每次初始化安装整套项目,包括安装 Nacos 和其他服务还有mysql,redis等其他中间件,安装后 Nacos 获取不到 nacos 路由信息(包括后续新写入动态路由配置)!只有 ...

  8. ps4双手柄inputManager设置

    ps4键位 https://blog.csdn.net/egostudio/article/details/51463819 xbox键位 http://www.360doc.com/content/ ...

  9. VUE环境运行搭建

    第一步:下载安装node.js 1.下载node.js,vue的运行是要依赖于node的npm的管理工具来实现,所以第一步我们需要安装 Node.js,访问官网 https://nodejs.org/ ...

  10. offer_48

    题目:写一个函数,求两个整数之和,要求在函数体内不得使用+.-.*./四则运算符号. 思路: /* 首先看十进制是如何做的: 5+7=12,三步走 第一步:相加各位的值,不算进位,得到2. 第二步:计 ...