题目链接

\(\Huge\text{无图,慎入}\)

\(FFT\)即快速傅里叶变换,用于加速多项式乘法。

如果暴力做卷积的话就是一个多项式的每个单项式去乘另一个多项式然后加起来,时间复杂度为\(O(n^2)\)。

\(FFT\)算法基本思想是把系数表达式转换成点值表达式,求出卷积的点值表达式,再转换回系数表达式。

何为点值表达式?

把多项式看成一个函数,比如\(n\)次多项式\(F\)可以看成一个\(n\)次函数\(F(x)=a_0+a_1x+a_2x^2+\cdots +a_nx^n\)

众所周知,知道\(n\)次函数上\(n+1\)个点的坐标一定可以求出这个\(n\)次函数的解析式。

用我们学过的知识一次函数、二次函数都可以验证。

硬要扩展到任意次函数的话也好解释,可以得到\(n+1\)个方程,用高斯消元就能解出来,当然肯定不会在\(FFT\)算法里出现,因为算法的目的是加速。

系数表达式->点值表达式的过程叫\(DFT\),点值表达式->系数表达式的过程叫\(IDFT\)。

先讲\(DFT\)。

怎么系数->点值?

代\(n\)个点是最直接的办法,但显然求一个点的值就是\(O(n)\)的,总时间复杂度为\(O(n^2)\)

这里要引入单位根概念,请确保了解复数的概念。

\(n\)次单位根记作\(\omega_n\),定义为\(n\)次方等于\(1\)的复数。

来推导一下性质。

首先\(n\)次方等于\(1\),这个复数的模长肯定是等于\(1\)的,所以在单位圆上。

其次,幅角\(\times n=2k\pi,k\in Z\)

脑补一下可以发现,\(n\)次单位根\(n\)等分单位圆,且\(1\)是一条等分线。

\(\omega_n^k\)表示从\(1\)开始逆时针旋转第\(i\)个\(n\)次单位根(从\(0\)开始)。

例如\(\omega_3^2\)就是把单位圆三等分,原点向正方向的射线是一条等分线,位于\(x\)轴下方的那条等分线。

不难发现,\(\forall n,\omega_n^0=1\) \(\forall n=2k,k\in Z, \omega_n^{\frac{n}{2}}=-1\)

理论上\(k\in [0,n)\),但类似于角度,也会出现超过\(360°\)或者负数的情况,同理也有\(\omega_n^k=\omega_n^{k\%n}\)

单位根的性质:

\(\omega_n^{a+b}=\omega_n^a\times \omega_n^b\),这个的解释就是复数相乘模长相乘幅角相加的法则。

有了这条,就能推出其他性质了。

\(\omega_n^k=\omega_{dn}^{dk}\)

\((\omega_n^k)^j=\omega_n^{jk}\)

步入正题:

\(DFT:\)

我们需要将\(n\)项多项式\(F(x)=a_0+a_1x+a_2x^2+\cdots +a_{n-1}x^{n-1}\)转成点值表达式。

假设\(n\)是\(2\)的正整数次幂。



\(FL(x)=a_0+a_2x+a_4x^2+\cdots+a_{n-2}x^{\frac{n}{2}-1}\)

\(FR(x)=a_1+a_3x+a_5x^2+\cdots+a_{n-1}x^{\frac{n}{2}-1}\)

易得\(F(x)=FL(x^2)+xFR(x^2)\)(自己代进去算一边就行了)

用\(\omega_n^k\)(\(k<\frac{n}{2}\))代入这个式子

\(\begin{align}F(\omega_n^k)&=FL(\omega_n^{2k})+\omega_n^kFR(\omega_n^{2k})\\&=FL(\omega_{\frac{n}{2}}^{k})+\omega_n^kFR(\omega_{\frac{n}{2}}^{k})\end{align}\)

这是\(k<\frac{n}{2}\)的情况,那如果\(k>=\frac{n}{2}\)呢?

\(\begin{align}F(\omega_n^{k+\frac{n}{2}})&=FL(\omega_n^{2k+n})+\omega_n^{k+\frac{n}{2}}FR(\omega_n^{2k+n})\\&=FL(\omega_{\frac{n}{2}}^{k})-\omega_n^kFR(\omega_{\frac{n}{2}}^{k})\end{align}\)

\(P.S:\omega_n^{k+\frac{n}{2}}=\omega_n^k\times \omega_n^{\frac{n}{2}}=-\omega_n^k\)

所以,如果我们知道了\(FL(\omega_{\frac{n}{2}}^{k})和FR(\omega_{\frac{n}{2}}^{k})\),就能求出\(F(\omega_n^k)\)和\(F(\omega_n^{k+\frac{n}{2}})\)

但是,我们怎么知道\(FL(\omega_{\frac{n}{2}}^{k})和FR(\omega_{\frac{n}{2}}^{k})\)呢?

难道这不像归并排序吗,分治啊!

实现过程中可能遇到的问题\(FAQ\)

1、怎么求\(\omega_n^k\)

只需要求出\(\omega_n^1\)他的\(k\)次方即\(\omega_n^k\)

2、怎么求\(\omega_n^1\)

\(\omega_n^1\)与\(1\)的夹角我们知道:\(\frac{2\pi}{n}\),然后又在单位圆上,解三角形啊!

告诉你结论就是\(\omega_n^1=cos(\frac{2\pi}{n})+sin(\frac{2\pi}{n})i\)

3、\(FL,FR\)数组从哪来

如果在递归中定义这两个数组显然会炸空间,于是蝴蝶操作诞生了。

咕咕咕。

但是,实际中\(n\)不一定是\(2\)的正整数次幂,我们只需要在最高位补\(0\)就行了,不影响结果。

现在我们求出了多项式的点值表达式,但这和他们的卷积有什么关系呢?

设\(H=F\times G\),\(H,F,G\)均为多项式

则\(H(x)=F(x)\times G(x)\)

所以如果我们知道两个多项式在\(x=\omega_n^0,\omega_n^1,\cdots,\omega_n^{n-1}\)的值,就能求出他们的卷积在\(x=\omega_n^0,\omega_n^1,\cdots,\omega_n^{n-1}\)的值,于是,第一步完成。

\(IDFT:\)

咕咕咕。

#include <cstdio>
#include <cmath>
#include <algorithm>
#define re register
using namespace std;
const int MAXN = 3000010;
const double PI = M_PI;
struct complex{
double x, y;
complex(double xx = 0, double yy = 0){ x = xx; y = yy; }
}a[MAXN], b[MAXN];
inline complex operator + (complex a, complex b){
return complex(a.x + b.x, a.y + b.y);
}
inline complex operator - (complex a, complex b){
return complex(a.x - b.x, a.y - b.y);
}
inline complex operator * (complex a, complex b){
return complex(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);
}
inline int read(){
re int s = 0, w = 1;
re char ch = getchar();
while(ch < '0' || ch > '9'){ ch = getchar(); if(ch == '-') w = -1; }
while(ch >= '0' && ch <= '9'){ s = s * 10 + ch - '0'; ch = getchar(); }
return s * w;
}
int r[MAXN], n, m;
void FFT(complex *f, int mode){
for(re int i = 0; i < n; ++i) if(i < r[i]) swap(f[i], f[r[i]]);
for(re int p = 2; p <= n; p <<= 1){
re int len = p >> 1;
re complex tmp(cos(PI / len), mode * sin(PI / len));
for(re int l = 0; l < n; l += p){
re complex w(1, 0);
for(re int k = l; k < l + len; ++k){
re complex t = w * f[len + k];
f[len + k] = f[k] - t;
f[k] = f[k] + t;
w = w * tmp;
}
}
}
}
inline double d(double x){
if(fabs(x) < 1e-9) return 0;
return x;
}
int main(){
n = read(); m = read();
for(re int i = 0; i <= n; ++i) a[i].x = read();
for(re int i = 0; i <= m; ++i) b[i].x = read();
for(m += n, n = 1; n <= m; n <<= 1);
for(re int i = 1; i < n; ++i) r[i] = r[i >> 1] >> 1 | ((i & 1) * (n >> 1));
FFT(a, 1); FFT(b, 1);
for(re int i = 0; i < n; ++i) a[i] = a[i] * b[i];
FFT(a, -1);
for(int i = 0; i <= m; ++i) printf("%.0f ", d(a[i].x / n));
return 0;
}

【总结】对FFT的理解 / 【洛谷 P3803】 【模板】多项式乘法(FFT)的更多相关文章

  1. 洛谷.3803.[模板]多项式乘法(FFT)

    题目链接:洛谷.LOJ. FFT相关:快速傅里叶变换(FFT)详解.FFT总结.从多项式乘法到快速傅里叶变换. 5.4 又看了一遍,这个也不错. 2019.3.7 叕看了一遍,推荐这个. #inclu ...

  2. 洛谷.3803.[模板]多项式乘法(NTT)

    题目链接:洛谷.LOJ. 为什么和那些差那么多啊.. 在这里记一下原根 Definition 阶 若\(a,p\)互质,且\(p>1\),我们称使\(a^n\equiv 1\ (mod\ p)\ ...

  3. P3803 [模板] 多项式乘法 (FFT)

    Rt 注意len要为2的幂 #include <bits/stdc++.h> using namespace std; const double PI = acos(-1.0); inli ...

  4. FFT/NTT总结+洛谷P3803 【模板】多项式乘法(FFT)(FFT/NTT)

    前言 众所周知,这两个东西都是用来算多项式乘法的. 对于这种常人思维难以理解的东西,就少些理解,多背板子吧! 因此只总结一下思路和代码,什么概念和推式子就靠巨佬们吧 推荐自为风月马前卒巨佬的概念和定理 ...

  5. 洛谷.4512.[模板]多项式除法(NTT)

    题目链接 多项式除法 & 取模 很神奇,记录一下. 只是主要部分,更详细的和其它内容看这吧. 给定一个\(n\)次多项式\(A(x)\)和\(m\)次多项式\(D(x)\),求\(deg(Q) ...

  6. 洛谷.4238.[模板]多项式求逆(NTT)

    题目链接 设多项式\(f(x)\)在模\(x^n\)下的逆元为\(g(x)\) \[f(x)g(x)\equiv 1\ (mod\ x^n)\] \[f(x)g(x)-1\equiv 0\ (mod\ ...

  7. 洛谷 P4512 [模板] 多项式除法

    题目:https://www.luogu.org/problemnew/show/P4512 看博客:https://www.cnblogs.com/owenyu/p/6724611.html htt ...

  8. 洛谷 P4238 [模板] 多项式求逆

    题目:https://www.luogu.org/problemnew/show/P4238 看博客:https://www.cnblogs.com/xiefengze1/p/9107752.html ...

  9. 洛谷p3803 FFT入门

    洛谷p3803 FFT入门 ps:花了我一天的时间弄懂fft的原理,感觉fft的折半很神奇! 大致谈一谈FFT的基本原理: 对于两个多项式的卷积,可以O(n^2)求出来(妥妥的暴力) 显然一个多项式可 ...

随机推荐

  1. 蜗牛慢慢爬 LeetCode 2. Add Two Numbers [Difficulty: Medium]

    题目 You are given two non-empty linked lists representing two non-negative integers. The digits are s ...

  2. 结对项目——fault,error,failure的程序设计

    一.结对编程内容: 1.不能触发Fault. 2.触发Fault,但是不触发Error. 3.触发Error,但不触发Failure. 二.结对编程人员 1.周宗耀.周浩: 2.结对截图: 三.结对项 ...

  3. Linux 常用指令【持续更新】

    在学校的时候学过一些简单的 Linux 命令,主要是文件的创建拷贝解压等操作,最近在电脑上安装了一个CentOS6.8版本的基本版,纯命令行操作. ../ 代表上一级目录 ./ 代表本级目录 / 代表 ...

  4. mybatis(一)MyBatis Generator

    在gradle中使用MyBatis Generator时,build.gradle配置如下: dependencies { mybatisGenerator group: 'org.mybatis.g ...

  5. 内存测试——Android Studio中对应进程的Heap

    通过Android Studio的Heap查看该程序的目前占用内存大小,多次进出界面,观察内存内存大小的变化.用Heap监测应用进程使用内存情况的步骤如下: 1. 启动Android Studio—& ...

  6. Android内存泄漏第二课--------(集合中对象没清理造成的内存泄漏 )

    一.我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大.如果这个集合是static的话,那情况就更严重 ...

  7. hdfs源码分析第二弹

    以写文件为例,串联整个流程的源码: FSDataOutputStream out = fs.create(outFile); 1. DistributedFileSystem 继承并实现了FileSy ...

  8. 【codevs1163】访问艺术馆 树形dp

    题目描述 皮尔是一个出了名的盗画者,他经过数月的精心准备,打算到艺术馆盗画.艺术馆的结构,每条走廊要么分叉为二条走廊,要么通向一个展览室.皮尔知道每个展室里藏画的数量,并且他精确地测量了通过每条走廊的 ...

  9. 创建Django的App

    一. 新建1个App,命令:python manage.py startapp lib 1. 打开终端 2. 新建 3. 把业务代码放到每一个APP里面就更专业了. 修改urls里面的代码如下: 运行 ...

  10. Unified Networking Lab 安装使用IOL镜像

    Unified Networking Lab 安装使用IOL镜像 Unified Networking Lab 很久以前,在一个星系远的地方,很远的工程师们为eBay寻找二手路由器来满足家庭实验的需求 ...