题目链接

\(\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. 结对项目之对PIE的测试程序

    项目要求: 构造程序,分别是: 不能触发Fault. 触发Fault,但是不能触发Error. 触发Error,但是不能产生Failure. 结对对象:陈秋月  学号:2013110404  博客地址 ...

  2. 【Leetcode】113Path Sum II

    Given a binary tree and a sum, find all root-to-leaf paths where each path's sum equals the given su ...

  3. phpcms 思维导图

    原文地址 : https://www.cnblogs.com/fuyunbiyi/archive/2012/03/12/2391253.html

  4. linux设置时区和自动同步时间

    1.设置时区 编辑 /etc/sysconfig/clock 修改 ZONE="Asia/Shanghai" 然后  cp  /usr/share/zoneinfo/Asia/Sh ...

  5. Bootstrap-tagsinput标系统使用心得

    最近工作中由于需求使用到了Bootstrap-tagsinput标系统,我的需求是: 1)能够从后台数据库获取标签信息展示到前端页面: 2)能够实现输入标签添加到后台,并ajax刷新页面: 3)能够实 ...

  6. script 执行的三种方式

    <script>: 脚本的获取和执行是同步的.此过程中页面被阻塞,停止解析. <script defer = "defer">:脚本的获取是异步的,执行是同 ...

  7. luogu 1360 阵容均衡(前缀和+差分+hash)

    要求一段最大的区间里每个能力的增长值是一样的. 我们首先求一遍前缀和,发现,如果区间内[l,r]每个能力的增长值是一样的话,那么前缀和[r]和[l-1]的差分也应该是一样的. 那么我们把前缀和的差分h ...

  8. Python网络编程socket

    网络编程之socket 看到本篇文章的题目是不是很疑惑,what is this?,不要着急,但是记住一说网络编程,你就想socket,socket是实现网络编程的工具,那么什么是socket,什么是 ...

  9. 数据结构开发(7):典型问题分析(Bugfix)

    0.目录 1.创建异常对象时的空指针问题 2.LinkList 中的数据元素删除 3.LinkList 中遍历操作与删除操作的混合使用 4.StaticLinkList 中数据元素删除时的效率问题 5 ...

  10. 待续--mysql中key 、primary key 、unique key 与index区别

    mysql中key .primary key .unique key 与index区别