FFT求多项式乘积

news/2025/1/11 19:41:27/

文章目录

  • 引入
  • 单位根
  • 快速傅里叶变换的数学证明
  • IFFT
  • 朴素版FFT函数代码
  • 优化FFT
  • 参考资料

引入

之前在b站上看到了一些介绍FFT的视频

《快速傅里叶变换(FFT)——有史以来最巧妙的算法?》
《这个算法改变了世界》

于是打算写一篇记录一下qwq(本博客中的截图基本上来源于第一个视频)

Fast Fourier Transform 是一种能在O(nlogn)O(nlogn)O(nlogn)的时间内将一个用系数表示的多项式转化成点值表示。

P(x)=p0+p1x+p2x2+...+pdxdP(x)=p_0+p_1x+p_2x^2+...+p_dx^dP(x)=p0+p1x+p2x2+...+pdxd

系数表示法:[p0,p1,p2,...,pd][p_0,p_1,p_2,...,p_d][p0,p1,p2,...,pd]
点值表示法:(x0,P(x0),(x1,P(x1),...,(xd,P(xd)){(x_0,P(x_0),(x_1,P(x_1),...,(x_d,P(x_d))}(x0,P(x0),(x1,P(x1),...,(xd,P(xd))

(n+1个不同点可以确定一个n次的多项式:可以用矩阵列式求解)

当我们试图算两个多项式相乘的结果:

A(x)=a0+a1x+...+adxdA(x)=a_0+a_1x+...+a_dx^dA(x)=a0+a1x+...+adxd
B(x)=b0+b1x+...+bdxdB(x)=b_0+b_1x+...+b_dx^dB(x)=b0+b1x+...+bdxd

传统的做法需要O(n2)O(n^2)O(n2),而如果我们先求出若干个点的坐标(多少个点根据最后算出的会是几次多项式而定,比如A是三次多项式,B是五次多项式,算出来的C会是八次多项式,那么就需要找九个点),最后再根据这些点把多项式还原成系数表示,但是对于每个点,知道一个横坐标,需要(d+1)次计算才能知道这个点的纵坐标,总复杂度还是会达到O(n2)O(n^2)O(n2),达咩。

主要过程

可不可以通过选取一些巧妙的点来减少我们的计算次数?
划分
我们可以每次将多项式分成奇项和偶项,通过这样的划分可以便捷地算出横坐标互为相反数的一对点对的坐标,此时我们需要求出x2x^2x2分别代入Pe(x2)P_e(x^2)Pe(x2)Po(x2)P_o(x^2)Po(x2)算出来的值,原问题变成了两个子问题,每个子问题的次数是原问题的1/21/21/2。我们想着能不能一直这么递归下去,直到分到底层(次数为1),再自底向上,层层归并求出答案?
递归
实际上在递归步骤,我们假设了每个多项式我们都使用相反数对来求值,然而对两个子问题而言,每个求值点都是平方数,没办法继续了(悲.jpg)于是我们希望把新的求值点也弄成相反数对QAQ

(P.S.为了方便讨论,下文的n均为2的整数次幂,如果题目的n不是2的整数次幂,我们可以加一波操作~)

于是——复数大法好!

单位根

四次方根
按照上面的逻辑推演,再向下递归一层我们需要两个相反点对,即要求出x4=1x^4=1x4=1的四个解;
八次方根
再向下递归一层我们需要四个相反点对,即要求出x8=1x^8=1x8=1的八个解;
现在推广到d阶多项式,我们要先取n>d个点,(并且n等于2的整数次幂),我们为求解多项式乘积所选取的点就是1的n个n次方根。
单位根
在这里插入图片描述

单位根的性质:

性质一:ω2n2k=ωnkω^{2k}_{2n}=ω^{k}_nω2n2k=ωnk

性质二:ωnk+n/2=−ωnkω^{k+n/2}_n=−ω^k_nωnk+n/2=ωnk(对应的点关于原点对称)

快速傅里叶变换的数学证明

A(x)=a0+a1x+a2x2+...+an−1xn−1A(x) = a_0 + a_1x + a_2x^2 +...+a_{n-1}x^{n-1}A(x)=a0+a1x+a2x2+...+an1xn1,为求离散傅里叶变换,要把一个x=ωnkx = \omega_n^kx=ωnk代入。

考虑将A(x)A(x)A(x)的每一项按照下标的奇偶分成两部分:

A(x)=(a0+a2x2+...+an−2xn−2)+(a1x+a3x3+...+an−1xn−1)A(x) = (a_0 + a_2x^2 + ... + a_{n - 2}x^{n - 2}) + (a_1x + a_3x^3 + ... + a_{n-1}x^{n-1})A(x)=(a0+a2x2+...+an2xn2)+(a1x+a3x3+...+an1xn1)

设两个多项式:
A1(x)=a0+a2x+...+an−2xn2−1A_1(x) = a_0 + a_2x + ... + a_{n - 2}x^{\frac{n}{2} - 1}A1(x)=a0+a2x+...+an2x2n1
A2(x)=a1+a3x+...+an−1xn2−1A_2(x) = a_1 + a_3x + ... + a_{n - 1}x^{\frac{n}{2} - 1}A2(x)=a1+a3x+...+an1x2n1
则:A(x)=A1(x2)+xA2(x2)A(x) = A_1(x^2) + xA_2(x^2)A(x)=A1(x2)+xA2(x2)

假设k<n2k < \frac{n}{2}k<2n,现要把x=ωnkx = \omega_n^kx=ωnk代入,

A(ωnk)=A1(ωn2k)+ωnkA2(ωn2k)=A1(ωn2k)+ωnkA2(ωn2k)\begin{align*} A(\omega_n^k) &= A_1(\omega_n^{2k}) + \omega_n^kA_2(\omega_n^{2k}) \\ &= A_1(\omega_{\frac{n}{2}}^{k}) + \omega_n^kA_2(\omega_{\frac{n}{2}}^{k}) \end{align*}A(ωnk)=A1(ωn2k)+ωnkA2(ωn2k)=A1(ω2nk)+ωnkA2(ω2nk)
那么对于A(ωnk+n2)A(\omega_n^{k + \frac{n}{2}})A(ωnk+2n):
A(ωnk+n2)=A1(ωn2k+n)+ωnk+n2A2(ωn2k+n)=A1(ωn2k×ωnn)+ωnk+n2A2(ωn2k×ωnn)=A1(ωn2k)−ωnkA2(ωn2k)\begin{align*} A(\omega_n^{k + \frac{n}{2}}) &= A_1(\omega_n^{2k + n}) + \omega_n^{k + \frac{n}{2}}A_2(\omega_n^{2k + n}) \\ &= A_1(\omega_{\frac{n}{2}}^{k} \times \omega_n^n) + \omega_n^{k + \frac{n}{2}} A_2(\omega_{\frac{n}{2}}^{k} \times \omega_n^n) \\ &= A_1(\omega_{\frac{n}{2}}^{k}) - \omega_n^kA_2(\omega_{\frac{n}{2}}^{k}) \end{align*}A(ωnk+2n)=A1(ωn2k+n)+ωnk+2nA2(ωn2k+n)=A1(ω2nk×ωnn)+ωnk+2nA2(ω2nk×ωnn)=A1(ω2nk)ωnkA2(ω2nk)

所以,如果我们知道两个多项式A1(x)A_1(x)A1(x)A2(x)A_2(x)A2(x)分别在(ωn20,ωn21,ωn22,...,ωn2n2−1)(\omega_{\frac{n}{2}}^{0}, \omega_{\frac{n}{2}}^{1}, \omega_{\frac{n}{2}}^{2}, ... , \omega_{\frac{n}{2}}^{\frac{n}{2} - 1})(ω2n0,ω2n1,ω2n2,...,ω2n2n1)的值,就可以求出A(x)A(x)A(x)ωn0,ωn1,ωn2,...,ωnn−1\omega_n^0, \omega_n^1, \omega_n^2, ..., \omega_n^{n-1}ωn0,ωn1,ωn2,...,ωnn1处的值了,A1(x)A_1(x)A1(x)A2(x)A_2(x)A2(x)是规模缩小一半的子问题,分治边界n=1n=1n=1.
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

IFFT

现在我们有了这若干个点的纵坐标,问题来到如何根据这些点的坐标还原答案多项式的系数,这需要用到IFFT(Inverse Fast Fourier Transform)

结论:把多项式A(x)A(x)A(x)的离散傅里叶变换结果作为另一个多项式B(x)B(x)B(x)的系数,取单位根的倒数即ωn0,ωn−1,ωn−2,...,ωn−(n−1)\omega_{n}^{0}, \omega_{n}^{-1}, \omega_{n}^{-2}, ..., \omega_{n}^{-(n - 1)}ωn0,ωn1,ωn2,...,ωn(n1)作为xxx代入B(x)B(x)B(x),得到的每个数再除以nnn,得到的是A(x)A(x)A(x)的各项系数。
(FFT的过程是求y矩阵,IFFT的过程是反过来求a矩阵,求出ω\omegaω矩阵的逆矩阵即可)
在这里插入图片描述

在这里插入图片描述

朴素版FFT函数代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;const ll N=1000010;
const double pi=acos(-1);
complex<double> a[N],b[N];
ll n,m;inline ll read(){ll x=0,tmp=1;char ch=getchar();while(!isdigit(ch)){if(ch=='-') tmp=-1;ch=getchar();}while(isdigit(ch)){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}return tmp*x;
}void fft(complex<double> *a,ll n,ll op)
{if(!n) return;complex<double> a0[n],a1[n];for(ll i=0; i<n; i++){a0[i]=a[i<<1];a1[i]=a[i<<1|1];}fft(a0,n>>1,op); fft(a1,n>>1,op);complex<double> W(cos(pi/n),sin(pi/n)*op),w(1,0);for(ll i=0;i<n;i++,w*=W){a[i]=a0[i]+w*a1[i];a[i+n]=a0[i]-w*a1[i];}
}int main()
{n=read(); m=read();for(ll i=0; i<=n; i++) a[i]=read();for(ll i=0; i<=m; i++) b[i]=read();for(m+=n,n=1; n<=m; n<<=1);fft(a,n>>1,1); fft(b,n>>1,1);for(ll i=0;i<n;i++) a[i]*=b[i];fft(a,n>>1,-1);for(ll i=0; i<=m; i++) printf("%.0lf ",fabs(a[i].real()/n));return 0;
}

优化FFT

在进行FFT时,我们要把各个系数不断分组并放到两侧,那么一个系数原来的位置和最终的位置有什么规律呢?

初始位置:0 1 2 3 4 5 6 7
第一轮后:0 2 4 6|1 3 5 7
第二轮后:0 4|2 6|1 5|3 7
第三轮后:0|4|2|6|1|5|3|7

“|”代表分组界限。一个位置a上的数,最后所在的位置是“a二进制翻转得到的数”,例如6(011)最后到了3(110),1(001)最后到了4(100)。

那么我们可以据此写出非递归版本FFT:先把每个数放到最后的位置上,然后不断向上还原,同时求出点值表示。

void fft(cp *a,int n,int inv)
{int bit=0;while((1<<bit)<n) bit++;for(int i=0;i<n;i++){rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));if(i<rev[i]) swap(a[i],a[rev[i]]);}for(int len=1;len<n;len*=2)//len是准备合并序列的长度的二分之一{cp temp(cos(pi/len),inv*sin(pi/len));for(int i=0;i<n;i+=len*2)//len*2是准备合并序列的长度,i是合并到了哪一位{cp omega(1,0);for(int j=0;j<len;j++,omega*=temp)//只扫左半部分,得到右半部分的答案{cp x=a[i+j],y=omega*a[i+j+len];a[i+j]=x+y,a[i+j+mid]=x-y;}}}
}
#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;const ll N=10000010;
const double pi=acos(-1);
ll n,m,limit;
complex<double> a[N],b[N];
ll c[N];inline ll read()
{ll x=0,tmp=1;char ch=getchar();while(!isdigit(ch)){if(ch=='-') tmp=-1;ch=getchar();}while(isdigit(ch)){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}return tmp*x;
}inline void write(ll x)
{if(x<0) putchar('-'), x=-x;ll y=10,len=1;while(y<=x) y=(y<<3)+(y<<1), len++;while(len--) y/=10, putchar(x/y+48),x%=y;
}void FFT(complex<double> *a,ll op)
{for(ll i=0; i<limit; i++)if(i<c[i]) swap(a[i],a[c[i]]);for(ll mid=1; mid<limit; mid<<=1){complex<double> W(cos(pi/mid),op*sin(pi/mid));for(ll r=mid<<1,j=0; j<limit; j+=r){complex<double> w(1,0);for(ll l=0; l<mid; l++,w*=W){complex<double> x=a[j+l],y=w*a[j+mid+l];a[j+l]=x+y; a[j+mid+l]=x-y;}}}
}int main()
{n=read(); m=read();for(ll i=0; i<=n; i++) a[i]=read();for(ll i=0; i<=m; i++) b[i]=read();limit=1; ll l=0;while(limit<=n+m)limit<<=1,l++;for(ll i=0;i<limit i++) c[i]=(c[i>>1]>>1)|((i&1)<<(l-1));FFT(a,1); FFT(b,1);for(ll i=0; i<=limit; i++) a[i]*=b[i];FFT(a,-1);for(ll i=0; i<=n+m; i++){write(a[i].real()/limit+0.5);putchar(' ');}return 0;
}

参考资料

参考资料:
小学生都能看懂的FFT!!!
《快速傅里叶变换(FFT)——有史以来最巧妙的算法?》
十分简明易懂的FFT(快速傅里叶变换)
多项式FFT


http://www.ppmy.cn/news/7238.html

相关文章

【Macbook】精选的MAC电脑软件推荐

1、终端软件&#xff1a;iTerm 下载地址&#xff1a;iTerm2 - macOS Terminal Replacement iTerm2 是一款功能强大的终端工具&#xff0c;也可以说是 Terminal 的替代品&#xff0c;也可以说是 iTerm 的后继产品。它适用于 macOS 10.12 或更高版本的 macOS。 它支持分窗口操作…

菜鸟也能懂的 - 音视频基础知识。

前言 说到视频&#xff0c;大家自己脑子里基本都会想起电影、电视剧、在线视频等等&#xff0c;也会想起一些视频格式 AVI、MP4、RMVB、MKV等等。 但是我们如果认真思考这些应该就有很多疑问&#xff0c;比如以下问题&#xff1a; mp4 和 mkv有什么区别 &#xff1f; 视频封装…

手绘图说电子元器件-电声转换器件

电声转换器件包括能够将电信号转换为声音的扬声器、耳机、讯响器和蜂鸣器,能够将声音转换为电信号的传声器,能够进行电磁转换的磁头和具有压电效应的晶体等。 扬声器 扬声器俗称喇叭,是一种常用的电声转换器件,其基本作用是将电信号转换为声音,在收音机、录音机、电视机…

6.1 函数基础

文章目录编写函数调用函数形参和实参函数的形参列表函数的返回类型局部对象自动对象局部静态对象函数声明在头文件中进行函数的声明分离式编译编译和链接多个源文件一个典型的函数 定义包括以下部分:返回类型、函数名字、由0个或多个形参组成的 列表以及函数体。其中&#xff0…

电商微信小程序的开发,项目及功能描述

开发类型&#xff1a;电商类小程序 项目名称&#xff1a;e家装猫商城 项目目标&#xff1a;本项目旨在开发一套基于微信小程序的线上电商平台&#xff0c;它将实现用户通过微信移动端选购自己需要的商品&#xff0c;商家后台获取用户订单信息来完成商品配送、核销的流程。同时商…

C++ thread的方式

多线程的实现方式&#xff0c;只做记录&#xff0c;自己看。 目录第一种 在类中实现多线程第二种 在类外第三种 没有类第四种 pthread,定时触发总结附录第一种 在类中实现多线程 新建thread对象&#xff0c;传入类的成员函数名称&#xff0c;对象地址&#xff0c;以及成员函数…

别在用BigDecimal给自己挖坑了!

前言 工作中&#xff0c;我们都会用到BigDecimal来进行金额计算&#xff0c;但是他有许多坑&#xff0c;可能针对新手不注意的话&#xff0c;就给自己多加几个bug了。一起来看看吧。 创建 new BigDecimal()还是BigDecimal#valueOf()&#xff1f; 创建对象的时候应该使用Big…

【AcWing寒假每日一题2023】Day1——孤独的照片

目录问题描述思路与代码问题描述 原题链接&#x1f517;&#xff1a;4261. 孤独的照片 Farmer John 最近购入了 NNN 头新的奶牛&#xff0c;每头奶牛的品种是更赛牛&#xff08;Guernsey&#xff09;或荷斯坦牛&#xff08;Holstein&#xff09;之一。 奶牛目前排成一排&#…