多项式

定义

形如\(A(x)=\sum_{i=0}^{n-1} a_i x^i\)的式子称为多项式。

我们把\(n\)称为该多项式的次数界。

显然,一个\(n-1\)次多项式的次数界为\(n\)。

运算法则

设\(A(x)\)和\(B(x)\)为多项式,且次数界分别为\(n\),\(m\)。则有:

\(A(x)=\sum_{i=0}^{n-1}a_i x^i\)

\(B(x)=\sum_{i=0}^{m-1}b_i x^i\)

他们遵循下面的常用运算法则:

\(A(x)+B(x)=\sum_{i=0}^{max(n-1,m-1)} (a_i+b_i)x^i\)

\(A(x)-B(x)=\sum_{i=0}^{max(n-1,m-1)} (a_i-b_i)x^i\)

\(A(x)B(x)=\sum_{k=0}^{n+m-2}\sum_{i=0}^{k}a_i b_{k-i} x^k\)

两个多项式的乘积称为他们的卷积。卷积也是一个多项式。

易证次数界分别为\(n\),\(m\)的多项式的卷积是一个次数界为\(n+m-1\)的多项式。

用定义计算卷积的复杂度为\(O(n^2)\)

使用快速傅里叶变换,可以使复杂度降为\(O(nlogn)\)

复数

代数形式

形如\(a+bi\)形式的数称为复数。其中\(i\)是\(\sqrt{-1}\)

我们可以使用平面上的点\((a,b)\)来描述一个复数。

复数的运算法则和向量相似。

三角形式

除了上文提到的代数形式,也有一种三角形式来表示复数。

如图所示。

我们知道复数\(a+bi\)对应着平面上的点\((a,b)\),也对应着复平面上的一个向量。

这个向量的长度叫做复数\(a+bi\)的模,记作\(|a+bi|\),通常用小写字母\(r\)表示。

这个向量与\(x\)正半轴有一个夹角,称为辐角,我们用希腊字母\(\theta\)表示。

显然有\(a=r\cos\theta\),\(b=r\sin\theta\)

把上式带入到代数表达式,可以得到\(a+bi=r\cos\theta+ir\sin\theta=r(\cos\theta+i\sin\theta)\)

定理:两复数相乘,模长相乘,辐角相加。

证明:

设\(z_1=r_1(\cos x+i\sin x)\),\(z_2=r_2(\cos y+i\sin y)\).

则\(z_1 z_2=r_1 r_2 [\cos x\cos y-\sin x\sin y+i(\cos x\sin y+\cos y\sin x)]=r_1 r_2[\cos(x+y)+i\sin(x+y)]\)

指数形式

欧拉公式:\(e^{i\theta}=\cos\theta+i\sin\theta\)

证明:

将\(e^x\),\(\sin x\),\(\cos x\)按照泰勒展开得到他们的泰勒级数:

\(e^x=1+\frac{x}{1!}+\frac{x^2}{2!}+\frac{x^3}{3!}+…\)

\(\cos x=1-\frac{x^2}{2!}+\frac{x^4}{4!}-\frac{x^6}{6!}+…\)

\(\sin x=x-\frac{x^3}{3!}+\frac{x^5}{5!}-\frac{x^7}{7!}\)

将\(x=i\theta\)带入得:

\(e^{i\theta}=1+\theta i-\frac{\theta^2}{2!}-\frac{\theta^3}{3!}i+\frac{\theta^4}{4!}+\frac{\theta^5}{5!}i-\frac{\theta^6}{6!}-\frac{\theta^7}{7!}i+…\)

\(e^{i\theta}=(1-\frac{\theta^2}{2!}+\frac{\theta^4}{4!}-\frac{\theta^6}{6!}+…)+i(\theta-\frac{\theta^3}{3!}+\frac{\theta^5}{5!}-\frac{\theta^7}{7!}+…)\)

即\(e^{i\theta}=\cos\theta+i\sin\theta\)

将欧拉公式带入到三角形式的表达式中就能得到复数的指数形式表达式:\(z=r(\cos\theta+i\sin\theta)=re^{i\theta}\)

单位复数根

在复数域内,有且仅有\(n\)个数,使得这个数的\(n\)次方等于1,我们把这样的数称为单位复数根,记作\(\omega_{n}^{i} (i\epsilon [0,n-1])\)

把\(\theta=2\pi\)带入欧拉公式,得\(e^{2\pi i}=1\)

所以\(\omega_{n}^{k}=(e^{\frac{2\pi i}{n}})^k\)

消去引理:\(\omega_{dn}^{dk}=\omega_{n}^{k}\)

证明:\(\omega_{dn}^{dk}=(e^{\frac{2\pi i}{dn}})^{dk}=(e^{\frac{2\pi i}{n}})^k=\omega_{n}^{k}\)

折半引理:\((\omega_{n}^{k})^2=\omega_{n/2}^{k}\)

证明:将\(d=\frac {1}{2}\)带入消去引理

求和引理:\(\sum_{i=0}^{n-1} (\omega_{n}^{k})^i=0\) (\(k\)不是\(n\)的倍数)

证明:利用等比数列求和公式,原式等于\(\frac{(\omega_{n}^{k})^n-1}{\omega_{n}^{k}-1}=\frac{0}{\omega_{n}^{k}-1}\)

因为分母不能为0,所以\(\omega_{n}^{k}\)不等于\(1\),所以\(k\)不是\(n\)的倍数.

循环性质:\((\omega_{n}^{k})^p=\omega_{n}^{pk}=\omega_{n}^{(pk\mod{n})}\)

证明:\(\omega_{n}^{kp}=\omega_{n}^{(kp\mod n+\left \lfloor \frac{kp}{n} \right \rfloor n)}=\omega_{n}^{(kp \mod n)}\)

对称性质:\(w_{n}^{k+\frac{n}{2}}=-w_{n}^{k}\)

证明:\(w_{n}^{k+\frac{n}{2}}=e^{\frac{2\pi ki}{n}+\pi i}=w_{n}^{k} e^{\pi i}=-w_{n}^{k}\)

离散傅里叶变换

多项式的两种表示方法

开头提到的多项式,都是用系数表达法来表示的。

也就是将次数界为\(n\)的多项式的系数\(a_0,a_1,…,a_{n-1}\)看成\(n\)维向量\(\vec{a}=(a_0,a_1,…,a_{n-1})\),\(\vec{a}\)称作多项式\(A(x)\)的系数表示。

事实上,除了系数表示\(A(x)=\sum_{i=0}^{n-1}a_i x^i\)还有一种表示方法:点值表示法。

一个次数界为\(n\)的多项式,就是\(n\)的点值对组成的集合。\((x_0,y_0),(x_1,y_1),…,(x_{n-1},y_{n-1})\) .对于\(0<=k<=n-1\),所有的\(x_k\)各不相同,记作\(y_k=A(x_k)\)

而离散傅里叶变换就是要快速求出多项式在这\(n\)的点的值

Cooley-Tukey算法

Cooley-Tukey算法是一种基于分治的算法

们取\(n\)个\(n\)次单位根 \(w_{n}^{0},w_{n}^{1},…,w_{n}^{n-1}\) 进行求值:\(A(w_{n}^{k})=\sum_{i=0}^{n-1}a_i w_{n}^{ki}\)

向量\(\vec{y}=(A(w_{n}^{0}),…,A(w_{n}^{n-1}))\)称作\(\vec{a}=(a_0,…,a_n-1)\)的离散傅里叶变换

接下来我们按照奇偶分开来算:

\(A(w_{n}^{k})=\sum_{i=0}^{n-1}a_i w_{n}^{ki}\)

\(=\sum_{i=0}^{\frac{n}{2}-1}a_{2i}w_{n}^{2ki}+w_{n}^{k}\sum_{i=0}^{\frac{n}{2}-1}a_{2i+1}w_{n}^{2ki}\)

\(=\sum_{i=0}^{\frac{n}{2}-1}a_{2i}w_{\frac{n}{2}}^{ki}+w_{n}^{k}\sum_{i=0}^{\frac{n}{2}-1}a_{2i+1}w_{\frac{n}{2}}^{ki}\) (折半引理)

而\(A(w_{n}^{k+\frac{n}{2}})=\sum_{i=0}^{\frac{n}{2}-1}a_{2i}w_{\frac{n}{2}}^{ki}-w_{n}^{k}\sum_{i=0}^{\frac{n}{2}-1}a_{2i+1}w_{\frac{n}{2}}^{ki}\)(对称性质)

这样问题就变成了求\(\frac{n}{2}\)个单位根的点值,问题的规模缩小了一半,所以是\(O(nlogn)\)的。

快速傅里叶变换的逆变换

求值与插值

对于一个多项式,如果知道它的系数表达式,我们很容易求出它的点值表达式,这一过程成为求值

如果知道它的点值表达式,让你求它的系数表达式,这一过程称为插值

定理:当插值多项式的次数界等于已知点值对的数目时,插值才是明确的

证明:\(y_k=A(x_k)\)等价于



其中,左边的矩阵称为范德蒙矩阵,其行列式\(Det=\prod_{0\leq j<k\leq n-1}(x_k-x_j)\)

如果所有\(x_k\)均不相等,那么其行列式不为\(0\),该矩阵可逆。

因此给定点值表达,能够唯一确定系数表达

所以如果我们知道了一个多项式的点值表示,就可以通过矩阵求逆的方法来算出系数向量

现在我们得到了点值表达式,那么如何求系数表达式呢?

矩阵求逆的复杂度是\(O(n^3)\)的,范德蒙矩阵求逆可以优化至\(O(n^2)\),但是复杂度依旧很高.

我们考虑构造一个现成的能算的东西进行优化

构造逆矩阵

我们现在已经得到了这样的方程:



记系数矩阵为\(V\),记下面的矩阵\(d_{ij}=w_{n}^{-ij}\)



它们相乘的结果\(E=DV\)满足\(e_{ij}=\sum_{k=0}^{n-1}w_{n}^{k(j-i)}\)

当\(i=j\)时,\(e_{ij}=n\)

当\(i\neq j\)时,\(e_{ij}=\sum_{k=0}^{n-1}(w_{n}^{(j-i)})^k=0\)(求和引理)

所以\(\frac{1}{n}D\)就是我们要求的逆矩阵,如下图:



这就相当于我们把单位复数根\(w^{k}_{n}\)换成\(w^{-k}_{n}\)再做一遍离散傅里叶变换结果除以\(n\)就行了

模板代码

  1. #include<cstdio>
  2. #include<algorithm>
  3. #include<cmath>
  4. #include<cstring>
  5. const int maxn = 1200000;
  6. const double pi = acos(-1.0);
  7. struct Complex {
  8. double r,i;
  9. Complex(double r,double i):r(r),i(i){}
  10. Complex(){};
  11. };
  12. Complex operator + (Complex a,Complex b) {
  13. return Complex(a.r+b.r,a.i+b.i);
  14. }
  15. Complex operator - (Complex a,Complex b) {
  16. return Complex(a.r-b.r,a.i-b.i);
  17. }
  18. Complex operator * (Complex a,Complex b) {
  19. return Complex(a.r*b.r-a.i*b.i,a.r*b.i+b.r*a.i);
  20. }
  21. int n,m,L[maxn],R[maxn];
  22. Complex A[maxn<<1],B[maxn<<1];
  23. inline void read(int &x) {
  24. char ch=getchar();x=0;
  25. while(ch<'0'||ch>'9')ch=getchar();
  26. while(ch>='0'&&ch<='9') {x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
  27. }
  28. inline void read(double &x) {
  29. char ch=getchar();x=0;
  30. while(ch<'0'||ch>'9')ch=getchar();
  31. while(ch>='0'&&ch<='9') {x=10*x+ch-'0';ch=getchar();}
  32. }
  33. void fft(Complex *a,int n,int inv) {
  34. for(register int i=1,j=n/2;i<n-1;++i) {
  35. if(i<j) std::swap(a[i],a[j]);
  36. int k = n>>1;
  37. while(j>=k) {
  38. j-=k;k>>=1;
  39. }
  40. j+=k;
  41. }
  42. for(register int j=2;j<=n;j<<=1) {
  43. Complex wn(cos(2*pi/j*inv),sin(2*pi/j*inv));
  44. for(register int i=0;i<n;i+=j) {
  45. Complex w(1,0);
  46. for(register int k=i;k<i+(j>>1);k++) {
  47. Complex u=a[k],t=a[k+(j>>1)]*w;
  48. a[k]=u+t;a[k+(j>>1)]=u-t;
  49. w=w*wn;
  50. }
  51. }
  52. }
  53. if(inv==-1) for(register int i=0;i<n;i++) a[i].r=a[i].r/n+0.5;
  54. }
  55. int ans[maxn<<1];
  56. int main() {
  57. read(n);read(m);
  58. for(register int i=0;i<=n;i++) read(A[i].r);
  59. for(register int i=0;i<=m;i++) read(B[i].r);
  60. int limit = 1;
  61. while(limit<=n+m) limit<<=1;
  62. fft(A,limit,1);fft(B,limit,1);
  63. for(register int i=0;i<=limit;i++) {
  64. A[i]=A[i]*B[i];
  65. }
  66. fft(A,limit,-1);
  67. for(register int i=0;i<=m+n;i++) printf("%llu ",(unsigned long long int)(A[i].r));
  68. return 0;
  69. }

【文文殿下】快速傅里叶变换(FFT)学习笔记的更多相关文章

  1. 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT)

    再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT) 目录 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Blueste ...

  2. 快速傅里叶变换(FFT)学习笔记(其一)

    再探快速傅里叶变换(FFT)学习笔记(其一) 目录 再探快速傅里叶变换(FFT)学习笔记(其一) 写在前面 为什么写这篇博客 一些约定 前置知识 多项式卷积 多项式的系数表达式和点值表达式 单位根及其 ...

  3. 快速傅里叶变换(FFT)学习笔记(其二)(NTT)

    再探快速傅里叶变换(FFT)学习笔记(其二)(NTT) 目录 再探快速傅里叶变换(FFT)学习笔记(其二)(NTT) 写在前面 一些约定 前置知识 同余类和剩余系 欧拉定理 阶 原根 求原根 NTT ...

  4. 快速傅里叶变换(FFT)学习笔记

    定义 多项式 系数表示法 设\(A(x)\)表示一个\(n-1\)次多项式,则所有项的系数组成的\(n\)维向量\((a_0,a_1,a_2,\dots,a_{n-1})\)唯一确定了这个多项式. 即 ...

  5. 【笔记篇】(理论向)快速傅里叶变换(FFT)学习笔记w

    现在真是一碰电脑就很颓废啊... 于是早晨把电脑锁上然后在旁边啃了一节课多的算导, 把FFT的基本原理整明白了.. 但是我并不觉得自己能讲明白... Fast Fourier Transformati ...

  6. 快速傅里叶变换FFT学习小记

    FFT学得还是有点模糊,原理那些基本还是算有所理解了吧,不过自己推这个推不动. 看的资料主要有这两个: http://blog.miskcoo.com/2015/04/polynomial-multi ...

  7. 【文文殿下】Manache算法-学习笔记

    Manache算法 定义:是一个判断回文子串的算法,我们结合例题解释: 题目:给定一个长度为 n 的字符串 S,求其最长回文子串 一个字符串是回文的,当且仅当反转后的串与原串完全相等 分析:对于这个题 ...

  8. [学习笔记] 多项式与快速傅里叶变换(FFT)基础

    引入 可能有不少OIer都知道FFT这个神奇的算法, 通过一系列玄学的变化就可以在 $O(nlog(n))$ 的总时间复杂度内计算出两个向量的卷积, 而代码量却非常小. 博主一年半前曾经因COGS的一 ...

  9. 【学习笔记】快速傅里叶变换(FFT)

    [学习笔记]快速傅里叶变换 学习之前先看懂这个 浅谈范德蒙德(Vandermonde)方阵的逆矩阵的求法以及快速傅里叶变换(FFT)中IDFT的原理--gzy hhh开个玩笑. 讲一下\(FFT\) ...

随机推荐

  1. Maven(三)理解Maven核心概念

    转载自: http://www.cnblogs.com/holbrook/archive/2012/12/24/2830519.html 本文以类图的方式,介绍maven核心的12个概念以及相互之间的 ...

  2. delphi datasnap

    http://blog.csdn.net/shuaihj/article/details/6129121 http://blog.csdn.net/ddqqyy/article/details/617 ...

  3. gRPC初探——概念介绍以及如何构建一个简单的gRPC服务

    目录 引言 1. gRPC简介 2. 使用Protocol Buffers进行服务定义 2.1 定义消息 2.2 定义服务接口 3.构建简单的gRPC服务 3.1 编写proto文件,定义消息和接口 ...

  4. Python all() 函数

    Python all() 函数  Python 内置函数 描述 all() 函数用于判断给定的可迭代参数 iterable 中的所有元素是否都为 TRUE,如果是返回 True,否则返回 False. ...

  5. mybatis调用存储过程获得取返回值

    总体思路:map传值 controller: Map<String,Object> m=new HashMap<String,Object>(); m.put("na ...

  6. Python:如何排序(sort)

    一.前言 对Python的列表(list)有两个用于排序的方法: 一个是内建方法list.sort(),可以直接改变列表的内容: >>> list1 = [9,8,7,6,5] &g ...

  7. PHP安装memcache扩展

    1.下载memcache.dll扩展 http://pecl.php.net/package/memcache/3.0.8/windows .下载完成之后, 将其中的php_memcache.dll ...

  8. eigen安装

    https://blog.csdn.net/liuxiaoheng1992/article/details/54410148

  9. Monocular Visual-Inertial Odometry单目视觉惯性里程计

    Monocular Visual-Inertial Odometry:https://www.qualcomm.com/invention/research 单目视觉-惯性里程计 INDOOR POS ...

  10. 测试用数据库表设计和SessionFactory

    本篇为struts-2.5.2和spring-3.2.0以及hibernate-4.2.21的整合开篇. 一.测试的数据库表. 用户.角色和权限关系表.数据库是Mysql5.6.为了考虑到一些特殊数据 ...