------------------------------------------
本文只探讨多项式乘法(FFT)在信息学中的应用
如有错误或不明欢迎指出或提问,在此不胜感激

多项式

1.系数表示法
     一般应用最广泛的表示方式
     用A(x)表示一个x-1次多项式,a[i]为$ x^i$的系数,则A(x)=$ \sum_0^{n-1}$ a[i] * $ x^i$

仅利用这种方式求多项式乘法复杂度为O($ n^2$),不够优秀
2.点值表示法
     将n个互不相同的值$ x_0$...$ x_{n-1}$带入多项式,可以得到
     对于一个n-1次多项式,可以被n个点所唯一对应.
     因而对于A(x)*B(x),只需要得知A的2n个点值和对应B的点值即可O(n)求出多项式的乘积
     然而得知这些点值的复杂度依然在平方级别,达不到要求

考虑优化
先引一些要用到的名词

复数
复数即为表示成a+bi的数,其中i为-1的平方根
表示:
     可以通过平面直角坐标系上的一条向量(0,0)到(x,y)表示x+yi
     其中x轴为实数轴,y轴为虚数轴
运算:
     复数运算符合四则运算,即:
     (a+bi) + (c+di) = (a+c) + (b+d)i;
     (a+bi) * (c+di) = ac + adi + bci + bd$ i^2$ =(ac - bd) + (bc + ad)i
几何意义:
     定义模长为向量长度,幅角为从x轴正半轴逆时针转动到向量的角
     复数相加等同于向量加法
     复数相乘,模长相乘,幅角相加
//建议complex类手写,速度优于STL

单位根
以下默认n为$ 2^x$ 且x为非负整数
在复数平面,以原点为圆心,以1为半径作圆
以x轴正半轴到其与原交点(0,0)到(1,0)的这条向量为起点n等分圆,圆心到每个n等分点的向量均称为n次单位根
对于一个单位根,其标号为幅角/(360°/n),特别的,(0,0)到(1,0)的向量标号为0
以下用$ w_n^k$ 表示标号为k的n次单位根

单位根的性质
     1. $ w_n^k$ = cos($ \frac{2π}{n}$)k + sin($ \frac{2π}{n}$)ki
     证明:欧拉公式

2. $ w_n^k$ = $ w_{2n}^{2k}$
     证明:带入式1,等价于分子分母同乘2,

3. $ w_n^k$ * $ w_n^k$=$ w_n^{2k}$
     证明:根据(复数相乘,模长相乘,幅角相加)
     又因为模长均为1,所以相当于只把转动角度乘2

4. $ w_n^{k+\frac{n}{2}}$ = -$ w_n^k$
     证明:$ w_n^{k+\frac{n}{2}}$相当于在$ w_n^k$的基础上逆时针方向再旋转180° (复数相乘,模长相乘,幅角相加)
     因而等价于取负

进入正题
递归完成快速傅里叶变换

设多项式A(x)的系数为$ a_0$...$ a_{n-1}$

A(x)=$ a_0$ + $ a_1$*x + $ a_2$*$ x^2$ + $ a_3$*$ x^3$ + $ a_4$*$ x^4$ + ... + $ a_{n-2}$*$ x^{n-2}$ + $ a_{n-1}$*$ x^{n-1}$

按下标奇偶性分成两组,在这里设
        A0(x) = $ a_0$ + $ a_2$ * $ x$ + $ a_4$ * $ x^2$ + ... + $ a_{n-2}$ * $ x^\frac{n-2}{2}$
        A1(x) = $ a_1$ + $ a_3$ * $ x$ + $ a_5$ * $ x^2$ + ... + $ a_{n-1}$ * $ x^\frac{n-2}{2}$

显然得到A(x) = A0($ x^2$) + xA1($ x^2$)
我们代单位根$ w_n^k$(0<=k<$ \frac{n}{2}$)入式得

A($ w_n^k$)=A0($ w_n^{2k}$)+$ w_n^k$A1($ w_n^{2k}$)//性质3

同理代$ w_n^{k+\frac{n}{2}}$入式得

A($ w_n^{k+\frac{n}{2}}$)=A0($ w_n^{2k+n}$)+$ w_n^{k+\frac{n}{2}}$A1($ w_n^{2k+n}$)
 = A0($ w_n^{2k}$) - $ w_n^k$ * A1($ w_n^{2k}$)//性质4&&$ w_n^n$=1

容易发现上下两式只有常数项的符号不同
因而只需求前一半即可得到后一半
递归形式程序结构:
对于长度为n的A(x)
分割成长度为$ \frac{n}{2}$的A0(x)和A1(x)
求A0(x)和A1(x)
通过A0(x)和A1(x)计算带入$ w_n^k$(0<=k<$ \frac{n}{2}$)时的值
变号计算代$ w_n^{k+\frac{n}{2}}$入式的值

我们可以用数组A[i]表示某一多项式(不一定是初始多项式)代入$ w_n^i$}时的值
由于许多奥妙重重的性质,我们不需要对于每个多项式都维护整个数组A,只需要维护它需要返回的值即可
绘图可得,当某一多项式递归到只有一项的时候,要返回的一定只是$ w_n^0$(1),即直接返回原多项式该项系数即可
递归代码:

void FFT(const int lim,cp *A){//cp即为complex类型,lim为2^n的整型if(lim==)return;//直接返回对应常数项
cp A0[lim>>],A1[lim>>];
for(rt i=;i<lim;i+=)A0[i>>]=A[i],A1[i>>]=A[i+];
FFT(lim>>,A0,fla);FFT(lim>>,A1,fla);//递归求解
cp w={cos(PI*2.0/lim),sin(PI*2.0/lim)},k={,};//w为1号单位根
for(rt i=;i<lim>>;i++,k=k*w){//k即为第i个单位根 A[i]=A0[i]+k*A1[i];//A0,A1数组中的i号本相当于A数组中的2i号,乘2再除2后相当于没有
A[i+(lim>>)]=A0[i]-k*A1[i];
}
}

逆变换
以上求得的均为点值表示法的结果
需要将其转回系数表示法
实际操作相当于再进行FFT时单位根逆向(顺时针)计算
递归全代码

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define rt register int
#define ll long long
using namespace std;
ll read(){
ll x = ; int zf = ; char ch;
while (ch != '-' && (ch < '' || ch > '')) ch = getchar();
if (ch == '-') zf = -, ch = getchar();
while (ch >= '' && ch <= '') x = x * + ch - '', ch = getchar(); return x * zf;
}
void write(ll y){
if (y < ) putchar('-'), y = -y;
if (y > ) write(y / );
putchar(y % + '');
}
int i,j,k,m,n,x,y,z,cnt,all,num;
struct cp{
double x,y;
}a[],b[];
inline cp operator +(const cp x,const cp y){return {x.x + y.x, x.y + y.y};}
inline cp operator *(const cp x,const cp y){return {x.x * y.x - x.y * y.y, x.x * y.y + x.y * y.x};}
inline cp operator -(const cp x,const cp y){return {x.x - y.x, x.y - y.y};}
const double PI=acos(-1.0);
void FFT(const int lim,cp *A,const int fla){//fla为-1表示逆变换
if(lim==)return;cp A1[lim>>],A2[lim>>];
for(rt i=;i<lim;i+=)A1[i>>]=A[i],A2[i>>]=A[i+];
FFT(lim>>,A1,fla);FFT(lim>>,A2,fla);
cp w={cos(PI*2.0/lim),sin(PI*2.0/lim)*fla},k={,};
for(rt i=;i<lim>>;i++,k=k*w){
A[i]=A1[i]+k*A2[i];
A[i+(lim>>)]=A1[i]-k*A2[i];
}
}
int main(){
n=read();m=read();
for(rt i=;i<=n;i++)a[i].x=read(),a[i].y=;
for(rt i=;i<=m;i++)b[i].x=read(),b[i].y=;
int limit=;while(limit<=n+m)limit<<=;//将多项式长度凑到2^n
FFT(limit,a,);FFT(limit,b,);
for(rt i=;i<=limit;i++)a[i]=a[i]*b[i];//a为点值表示的多项式
FFT(limit,a,-);//逆变换
for(rt i=;i<=n+m;i++)write(a[i].x/limit+0.5),putchar(' ');
//因为有limit个单位根因而答案需要除limit,0.5是四舍五入
return ;
}

问题:常数巨大
解决方案:改成非递归(迭代)形式
观察下图


发现底层序列的值相当于原序列的值的二进制反转
因而我们可以预处理底层的值然后迭代向上推

如何预处理
1.x为偶数
     x=(x>>1)<<1;
     在反转序列中reverse[x]=reverse[x>>1]>>1;
2.x为奇数
     相当于x-1的结果再在最高位补1

这样我们得到底层结果之后,一层一层向上迭代合并即可
迭代代码:

#include<ctime>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define rt register int
#define ll long long
using namespace std;
inline ll read(){
ll x = ; char zf = ; char ch = getchar();
while (ch != '-' && !isdigit(ch)) ch = getchar();
if (ch == '-') zf = -, ch = getchar();
while (isdigit(ch)) x = x * + ch - '', ch = getchar(); return x * zf;
}
void write(ll y){if(y<)putchar('-'),y=-y;if(y>)write(y/);putchar(y%+);}
void writeln(const ll y){write(y);putchar('\n');}
int i,j,k,m,n,x,y,z,cnt,lim;
struct cp{
double x,y;
cp operator +(const cp s)const{
return (cp){x+s.x,y+s.y};
}
cp operator -(const cp s)const{
return (cp){x-s.x,y-s.y};
}
cp operator *(const cp s)const{
return (cp){x*s.x-y*s.y,x*s.y+y*s.x};
}
}a[],b[];
int R[];
const double PI=acos(-1.0);
void FFT(cp *A,int fla){
for(rt i=;i<lim;i++)if(i>R[i])swap(A[i],A[R[i]]);
for(rt i=;i<lim;i<<=){
cp w={cos(PI/i),fla*sin(PI/i)};
for(rt j=;j<lim;j+=i<<){
cp K={,};
for(rt k=;k<i;k++,K=K*w){
cp x=A[j+k],y=K*A[i+j+k];
A[j+k]=x+y,A[i+j+k]=x-y;
}
}
}
}
int main(){
n=read();m=read();lim=;
for(rt i=;i<=n;i++)a[i].x=read();
for(rt i=;i<=m;i++)b[i].x=read();
while(lim<=n+m)lim<<=;
for(rt i=;i<lim;i++)R[i]=(R[i>>]>>)|((i&)*lim>>);
FFT(a,);FFT(b,);
for(rt i=;i<lim;i++)a[i]=a[i]*b[i];
FFT(a,-);
for(rt i=;i<=n+m;i++)write(a[i].x/lim+0.5),putchar(' ');
return ;
}

多项式乘法(FFT)学习笔记的更多相关文章

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

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

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

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

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

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

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

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

  5. 【learning】多项式乘法&fft

    [吐槽] 以前一直觉得这个东西十分高端完全不会qwq 但是向lyy.yxq.yww.dtz等dalao们学习之后发现这个东西的代码实现其实极其简洁 于是趁着还没有忘记赶紧来写一篇博 (说起来这篇东西的 ...

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

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

  7. Servlet乘法表学习笔记

    一.控制台实现乘法表 package com.shanrengo; import java.io.IOException; import java.io.PrintWriter; import jav ...

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

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

  9. @总结 - 1@ 多项式乘法 —— FFT

    目录 @0 - 参考资料@ @1 - 一些概念@ @2 - 傅里叶正变换@ @3 - 傅里叶逆变换@ @4 - 迭代实现 FFT@ @5 - 参考代码实现@ @6 - 快速数论变换 NTT@ @7 - ...

随机推荐

  1. 一个时间上的比较 if else

    if (w<b.w) ; if (w>b.w) ; if (w<b.w) ; else if (w>b.w) ; 对于任何情况,执行的次数都是一样.只是对于汇编的代码,第二个方 ...

  2. 第三节,使用OpenCV 3处理图像(模糊滤波、边缘检测)

    一 不同色彩空间的转换 OpenCV中有数百种关于在不同色彩空间之间转换的方法.当前,在计算机中有三种常用的色彩空间:灰度,BGR以及HSV(Hue,Saturation,Value). 灰度色彩空间 ...

  3. C语言进阶--Day2

    今天主要讲解的是函数的压栈与出栈 1. 要实现一个数组的逆置,用栈的压栈出栈观点: reverseArr(int *parr,int i,int len) { if(i != len-1) rever ...

  4. shell之case

    在shell变成中,case语句是if语句的一种扩展,将if中的判断语句,展开,同一个变量,对应多个可能的值时,执行不同的操作.具体句型如下: case "变量" in value ...

  5. python自动化开发-[第十四天]-javascript(续)

    今日概要: 1.数据类型 2.函数function 3.BOM 4.DOM 1.运算符 算术运算符: + - * / % ++ -- 比较运算符: > >= < <= != = ...

  6. zookeeper 介绍与集群安装

    zookeeper 介绍 ZooKeeper是一个分布式开源框架,提供了协调分布式应用的基本服务,它向外部应用暴露一组通用服务——分布式同步(Distributed Synchronization). ...

  7. java类的编译、加载和执行

    一.java类的编译流程 这里主要讲的是从java文件到class文件 下图是java类编译的详细步骤: 1.词法分析:将java源代码的字符流转变为标记(Token)的集合,Token是编译过程中的 ...

  8. 【转】第8章 前摄器(Proactor):用于为异步事件多路分离和分派处理器的对象行为模式

    目录: Reactor(反应堆)和Proactor(前摄器) <I/O模型之三:两种高性能 I/O 设计模式 Reactor 和 Proactor> <[转]第8章 前摄器(Proa ...

  9. MyBatis-获取 SqlSession

    Main 方法,mybatis 版本为 3.5.0 返回一个 DefaultSQlSession 对象,包含 Executor 和 Configuration InputStream inputStr ...

  10. C++回顾day01---<命名空间>

    一:namespace是指标识符的各种控件范围(类java中package) C++语言引入命名空间(Namespace)这一概念主要是为了避免命名冲突,其关键字为 namespace 二:iostr ...