多项式乘法(FFT)详解

article/2025/8/15 4:16:49

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

多项式
1. 系数表示法
一般应用最广泛的表示方式
用A(x)表示一个x-1次多项式,a[i]为 xi x i 的系数,则A(x)= n10 ∑ 0 n − 1 a[i] * xi x i
仅利用这种方式求多项式乘法复杂度为O( n2 n 2 ),不够优秀
2.点值表示法
将n个互不相同的值 x0 x 0 xn1 x n − 1 带入多项式,可以得到
对于一个n-1次多项式,可以被n个点所唯一对应.
因而对于A(x)*B(x),只需要得知A的2n个点值和对应B的2n个点值即可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 i2 i 2 =(ac - bd) + (bc + ad)i
几何意义:
定义模长为向量长度,幅角为从x轴正半轴逆时针转动到向量的角
复数相加等同于向量加法
复数相乘,模长相乘,幅角相加
//建议complex类手写,速度优于STL

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

单位根的性质
1. wkn w n k = cos( 2πn 2 π n )k + sin( 2πn 2 π n )ki
证明:欧拉公式
2. wkn w n k = w2k2n w 2 n 2 k
证明:带入式1,等价于分子分母同乘2,
3. wkn w n k * wkn w n k = w2kn w n 2 k
证明:根据(复数相乘,模长相乘,幅角相加)
又因为模长均为1,所以相当于只把转动角度乘2
4. wk+n2n w n k + n 2 = - wkn w n k
证明: wk+n2n w n k + n 2 相当于在 wkn w n k 的基础上逆时针方向再旋转180° (复数相乘,模长相乘,幅角相加)
因而等价于取负

进入正题
递归完成快速傅里叶变换
这里写图片描述

设多项式A(x)的系数为 a0 a 0 an1 a n − 1
则A(x) = a0 a 0 + a1 a 1 * x + a2 a 2 * x2 x 2 + a3 a 3 * x3 x 3 + a4 a 4 * x4 x 4 + … + an2 a n − 2 * xn2 x n − 2 + an1 a n − 1 * xn1 x n − 1

按下标奇偶性分成两组,在这里设
A0(x) = a0 a 0 + a2 a 2 * x x + a4 * x2 x 2 + … + an2 a n − 2 * xn22 x n − 2 2
A1(x) = a1 a 1 + a3 a 3 * x x + a5 * x2 x 2 + … + an1 a n − 1 * xn22 x n − 2 2

显然得到A(x) = A0( x2 x 2 ) + xA1( x2 x 2 )
我们代单位根 wkn w n k (0<=k< n2 n 2 )入式得

A( wkn w n k )=A0( w2kn w n 2 k )+ wkn w n k A1( w2kn w n 2 k )//性质3
同理代 wk+n2n w n k + n 2 入式得
A( wk+n2n w n k + n 2 )=A0( w2k+nn w n 2 k + n )+ wk+n2n w n k + n 2 A1( w2k+nn w n 2 k + n )
                 = A0( w2kn w n 2 k ) - wkn w n k * A1( w2kn w n 2 k )//性质4&& wnn w n n =1

容易发现上下两式只有常数项的符号不同
因而只需求前一般即可得到后一般
递归形式程序结构:
对于长度为n的A(x)
分割成长度为 n2 n 2 的A0(x)和A1(x)
求A0(x)和A1(x)
通过A0(x)和A1(x)计算带入 wkn w n k (0<=k< n2 n 2 )时的值
变号计算代 wk+n2n w n k + n 2 入式的值
我们可以用数组A[i]表示某一多项式(不一定是初始多项式)代入 win w n i }时的值
由于许多奥妙重重的性质,我们不需要对于每个多项式都维护整个数组A,只需要维护它需要返回的值即可
绘图可得,当某一多项式递归到只有一项的时候,要返回的一定只是 w0n w n 0 (1),即直接返回原多项式该项系数即可
递归代码:

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

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

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

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

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

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

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

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define rt register int
#define ll long long
#define r read()
using namespace std;
ll read()
{ll x = 0; int zf = 1; char ch;while (ch != '-' && (ch < '0' || ch > '9')) ch = getchar();if (ch == '-') zf = -1, ch = getchar();while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar(); return x * zf;
}
void write(ll y)
{if (y < 0) putchar('-'), y = -y;if (y > 9) write(y / 10);putchar(y % 10 + '0');
}
inline void writeln(ll x)
{write(x);putchar('\n');
}
int i,j,k,m,n,x,y,z,cnt,all,num,li,lim;
int low[2200010];//low数组就是底层的reverse序列 
struct cp{double x,y;
}a[2200010],b[2200010];
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};}
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};}
double PI=acos(-1.0);
void FFT(cp *A,const int fla)
{for(rt i=0;i<lim;i++)if(i<low[i])swap(A[i],A[low[i]]);//得到底层数列 for(rt i=1;i<lim;i<<=1)//i表示当前层的下面每个区间大小为i {cp w={cos(PI/i),fla*sin(PI/i)};//本应为2PI/2i for(rt j=0;j<lim;j+=(i<<1))//j为当前层的每个起始端点 {cp K={1,0};for(rt k=0;k<i;k++,K=K*w)//k枚举当前层的j所在区间的元素 {const cp x=A[j+k],y=K*A[j+k+i];//求解,如果对递归理解深刻应该能理解这段 A[j+k]=x+y;A[j+k+i]=x-y;}}}
}
int main()
{n=r;m=r;for(rt i=0;i<=n;i++)a[i].x=r;for(rt i=0;i<=m;i++)b[i].x=r;lim=1;while(lim<=n+m)lim*=2,li++;for(rt i=0;i<lim;i++)low[i]=(low[i>>1]>>1)+((i&1)<<(li-1));FFT(a,1);FFT(b,1);for(rt i=0;i<lim;i++)a[i]=a[i]*b[i];FFT(a,-1);for(rt i=0;i<=(n+m);i++)printf("%d ",(int)(a[i].x/lim+0.5));return 0;
}

http://chatgpt.dhexx.cn/article/8UPl8S6N.shtml

相关文章

2.2 多项式乘法与加法运算(线性结构,C)

多项式乘法与加法运算 设计函数分别求两个一元多项式的乘积与和题意理解题意理解和积 求解思路多项式表示两种表示方式在事先已经知道具体多少项的时候&#xff0c;本题较好的实现方法&#xff1a;动态数组链表表示多项式的方法 程序框架如何读入多项式读入多项式的完整程序 加…

多项式乘法问题

多项式乘法问题 实验目的&#xff1a;设计一个一元稀疏多项式简单计算器。 实验内容与要求&#xff1a; 一元稀疏多项式简单计算器的基本功能是&#xff1a; &#xff08;1&#xff09;输入并建立多项式&#xff1b; &#xff08;2&#xff09;输出多项式&#xff0c;序列…

网络协议之视频直播核心技术讲解

网络视频直播存在已有很长一段时间&#xff0c;随着移动上下行带宽提升及资费的下调&#xff0c;视频直播被赋予了更多娱乐和社交的属性&#xff0c;人们享受随时随地进行直播和观看&#xff0c;直播的打开时间和延迟变成了影响产品功能发展重要指标。 那么&#xff0c;问题来了…

直播软件搭建技术原理:CDN 与直播

直播软件搭建技术原理&#xff1a;CDN 与直播 很多直播都是基于 CDN 来实现的。而通过声网的服务&#xff0c;或基于声网SDK与 CDN 结合&#xff0c;还可以实现在直播中的连麦互动、白板同步等强调实时性的场景。本文源自社区投稿&#xff0c;介绍了该场景下的一些基础知识。如…

暑期实习+秋招面经合集(更新ing)

大纲 开篇 自我介绍 &#xff1a;面试官你好&#xff0c;我叫林飞武&#xff0c;是一名通信工程大三学生&#xff0c;对计算机专业有着浓厚兴趣并且未来有志于在互联网的测试领域有深入发展。全套学习了计算机专业的专业课&#xff0c;计算机网络&#xff0c;操作系统等&#…

DNSPod十问王征:为什么你的网站无人问津?

&#x1f4e2; DNSPod十八周年庆将至&#xff0c;下期十问交给你来提问——《你&#xff0c;十问DNSPod》&#xff01;评论区留下你想问DNSPod团队的问题&#xff0c;一旦提问被选中&#xff0c;将得到十八周年纪念T恤&#xff01;详情请移步至DNSPod公众号十八周年庆推送。 广…

阿里云ACP认证考试笔记

课件&#xff1a;https://gitee.com/HanFerm/technical-documentation/tree/master/阿里云acp教材 本文档为公开内容 一、ACP是干嘛的 内容范围&#xff1a; 历史 二、阿里云综述 技术架构 优势 三、弹性计算 ECS ECS的组成与功能 ECS是由多个并列又相互关联的产品概念组成…

在互联网上,没有人知道你是一条狗?

1993 年&#xff0c;《纽约客》&#xff08;The New Yorker&#xff09;杂志刊登一则由彼得施泰纳&#xff08;Peter Steiner&#xff09;创作的漫画&#xff1a;标题是【On the Internet, nobody knows you’re a dog.】 这则漫画中有两只狗&#xff1a;一只黑狗站在电脑椅上…

分库分表和NewSQL如何选择?分库分表真的适合你的系统吗?

曾几何时&#xff0c;“并发高就分库&#xff0c;数据大就分表”已经成了处理 MySQL 数据增长问题的圣经。 面试官喜欢问&#xff0c;博主喜欢写&#xff0c;候选人也喜欢背&#xff0c;似乎已经形成了一个闭环。 但你有没有思考过&#xff0c;分库分表真的适合你的系统吗&am…

分库分表不一定适合你的系统,聊聊分库分表和NewSQL如何选择

曾几何时&#xff0c;“并发高就分库&#xff0c;数据大就分表”已经成了处理 MySQL 数据增长问题的圣经。 面试官喜欢问&#xff0c;博主喜欢写&#xff0c;候选人也喜欢背&#xff0c;似乎已经形成了一个闭环。 但你有没有思考过&#xff0c;分库分表真的适合你的系统吗&am…

分库分表真的适合你的系统吗?聊聊分库分表和NewSQL如何选择

曾几何时&#xff0c;“并发高就分库&#xff0c;数据大就分表”已经成了处理 MySQL 数据增长问题的圣经。 面试官喜欢问&#xff0c;博主喜欢写&#xff0c;候选人也喜欢背&#xff0c;似乎已经形成了一个闭环。 但你有没有思考过&#xff0c;分库分表真的适合你的系统吗&am…

分库分表和 NewSQL 到底怎么选?

文章来源&#xff1a;【公众号&#xff1a;CoderW】 目录 背景分表分库分库分表的成本NewSQLNewSQL 平滑接入方案NewSQL 真的有那么好吗&#xff1f;NewSQL 的应用分库分表和 NewSQL 到底怎么选&#xff1f; 背景 曾几何时&#xff0c;“并发高就分库&#xff0c;数据大就分表”…

软考初级-信息处理技术员

22年下半年也是顺利通过了软考初级-信息处理技术员&#xff0c;明年再报一次软设&#xff0c;今年上半年软设下午题没过&#xff0c;总结还是代码敲的少&#xff0c;想的太多hh&#xff0c;下面来看看我总结的上午题考点&#xff0c;非常感谢我好友老郑教我指点了我excel的函数…

学术交流 | InForSec 2023年网络空间安全国际学术研究成果分享及青年学者论坛

隐私计算研习社 InForSec定于2023年4月8日&#xff5e;9日&#xff08;周六、日&#xff09;在南方科技大学举办“InForSec 2023年网络空间安全国际学术研究成果分享及青年学者论坛”。本次学术活动将邀请在网络空间安全顶级会议上发表论文的研究者分享他们的最新研究成果&…

用matlab绘制光滑曲线(plot画出的为折线)

x[0 0.1 0.16 0.27 0.41 0.48 0.59 0.8] y[5 9 70 118 100 17 0 5]; 那么用plot画出的函数为折线&#xff0c;如下图&#xff1a; 要想把那个折点平滑掉。像论文中那样&#xff0c;具体采用样条函数&#xff1a;下面是样条函数的定义&#xff1a; spline function 一类分段&…

matlab plot画曲线/直线/椭圆

本博文源于matlab基础&#xff0c;每个图像一个案例引入&#xff0c;大家简单看&#xff0c;直接照猫画虎去套用就行了。 画直线 例子&#xff1a;画y2*x3 范围为[1,10] 代码&#xff1a; >> x1:10; >> y2*x3; >> plot(y) >> 画曲线 在区间[-Π,Π…

Matlab图形绘制(一)三维曲线

文章目录 1.三维曲线常用函数第一个例子第二个例子第三个例子&#xff1a;&#xff08;更改线性&#xff0c;颜色&#xff09;第四个例子&#xff1a;&#xff08;有返回值的情况&#xff09; 1.三维曲线常用函数 plot3函数&#xff0c;用于绘制3D图形的一个非常常用的函数。 …

【MATLAB】动态绘制曲线图(二维曲线)

先看效果✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨ 主程序&#xff1a; 加载数据的部分我省略了&#xff0c;就是data1这个矩阵 close all; X1:25; set(gcf,unit,normalized,position,[0.3,0.25,0.5,0.5]); %figure窗口位置、大小设置 ylabel(人数) xlabel(日期) title(2022年11月重庆…

MATLAB----绘制三维曲线

参考于:中国大学慕课科学计算于MATLAB语言专题四“4.4三维曲线” 1.plot3函数 plot3(x,y,z,选项) plot3用来绘制三维曲线&#xff0c;与plot用法类似。当x&#xff0c;y&#xff0c;z为同型矩阵时&#xff0c;以x&#xff0c;y&#xff0c;z对应列绘制曲线&#xff0c;曲线条…

matlab画平滑曲线的两种方法

自然状态下&#xff0c;用plot画的是折线&#xff0c;而不是平滑曲线。 有两种方法可以画平滑曲线&#xff0c;第一种是拟合的方法&#xff0c;第二种是用spcrv&#xff0c;其实原理应该都一样就是插值。下面是源程序&#xff0c;大家可以根据需要自行选择&#xff0c;更改拟合…