【总结】对FFT的理解 / 【洛谷 P3803】 【模板】多项式乘法(FFT)
题目链接
\(\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)的更多相关文章
- 洛谷.3803.[模板]多项式乘法(FFT)
题目链接:洛谷.LOJ. FFT相关:快速傅里叶变换(FFT)详解.FFT总结.从多项式乘法到快速傅里叶变换. 5.4 又看了一遍,这个也不错. 2019.3.7 叕看了一遍,推荐这个. #inclu ...
- 洛谷.3803.[模板]多项式乘法(NTT)
题目链接:洛谷.LOJ. 为什么和那些差那么多啊.. 在这里记一下原根 Definition 阶 若\(a,p\)互质,且\(p>1\),我们称使\(a^n\equiv 1\ (mod\ p)\ ...
- P3803 [模板] 多项式乘法 (FFT)
Rt 注意len要为2的幂 #include <bits/stdc++.h> using namespace std; const double PI = acos(-1.0); inli ...
- FFT/NTT总结+洛谷P3803 【模板】多项式乘法(FFT)(FFT/NTT)
前言 众所周知,这两个东西都是用来算多项式乘法的. 对于这种常人思维难以理解的东西,就少些理解,多背板子吧! 因此只总结一下思路和代码,什么概念和推式子就靠巨佬们吧 推荐自为风月马前卒巨佬的概念和定理 ...
- 洛谷.4512.[模板]多项式除法(NTT)
题目链接 多项式除法 & 取模 很神奇,记录一下. 只是主要部分,更详细的和其它内容看这吧. 给定一个\(n\)次多项式\(A(x)\)和\(m\)次多项式\(D(x)\),求\(deg(Q) ...
- 洛谷.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\ ...
- 洛谷 P4512 [模板] 多项式除法
题目:https://www.luogu.org/problemnew/show/P4512 看博客:https://www.cnblogs.com/owenyu/p/6724611.html htt ...
- 洛谷 P4238 [模板] 多项式求逆
题目:https://www.luogu.org/problemnew/show/P4238 看博客:https://www.cnblogs.com/xiefengze1/p/9107752.html ...
- 洛谷p3803 FFT入门
洛谷p3803 FFT入门 ps:花了我一天的时间弄懂fft的原理,感觉fft的折半很神奇! 大致谈一谈FFT的基本原理: 对于两个多项式的卷积,可以O(n^2)求出来(妥妥的暴力) 显然一个多项式可 ...
随机推荐
- selenium webdriver XPath的定位方法练习 !
html 代码: <html> <body> <div id="div1"> <input name="divl1input& ...
- 微信获取 openid 静默及非静默
<?php /* 需要的微信公众号配置信息 APPID : 绑定支付的APPID APPSECRET : 公众帐号secert */ class Index { // 配置账号信息 privat ...
- PHP之implode()方法
implode — 将一个一维数组的值转化为字符串 string implode ( string $glue , array $pieces ) string implode ( array $pi ...
- 【BZOJ4129】Haruna’s Breakfast(树上莫队)
[BZOJ4129]Haruna's Breakfast(树上莫队) 题面 BZOJ Description Haruna每天都会给提督做早餐! 这天她发现早饭的食材被调皮的 Shimakaze放到了 ...
- BZOJ1176:[Balkan2007]Mokia——题解
http://www.lydsy.com/JudgeOnline/problem.php?id=1176 Description(题面本人自行修改了一下) 维护一个W*W的矩阵,初始值均为0.每次操作 ...
- 【原创】【目录】实现rich editor(富文本编辑器)教程,深入理解selectiona/range操作与浏览器差异
日常工作中,接触富文本编辑的次数还是很多,特发此教程,希望可以改变富文本编辑的技术门槛较高的现状,解决这部分的坑. 前提准备,兼容获取range,统一回车行为,前期准备工作 了解document.ex ...
- 【bzoj3170】[Tjoi2013]松鼠聚会
3170: [Tjoi2013]松鼠聚会 Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 1670 Solved: 885[Submit][Statu ...
- Python之旅:装饰器
装饰器就是闭包函数的一种应用场景 一.为何要用装饰器 开放封闭原则:软件一旦上线后,就应该遵循开放封闭原则,即对修改源代码是封闭的,对功能的扩展是开放的 也就是说我们必须找到一种解决方案:能够在不修该 ...
- mac命令行快捷键
其实不想每次输入host和user,可以在 ~/.ssh/config文件写上配置alias信息,以后ssh的时候根据alias即可.如: Host alias-name HostName ip_ad ...
- C++ ------ static_cast,dynamic_cast,reinterpret_cast,const_cast
C++类型转换分为:隐式类型转换和显式类型转换 第1部分. 隐式类型转换 又称为“标准转换”,包括以下几种情况:1) 算术转换(Arithmetic conversion) : 在混合类型的算术表达式 ...