题解 P3803 【模板】多项式乘法(FFT)
感觉题解区不是写的太高深,就是写的太高深。所以给初中、小学和幼儿园的萌新准备一篇简单易懂的良心题解~
前置知识
一、多项式的系数表示法和点值表示法。
系数:。
点值:。易证, 个点确定一个多项式。
二、向量的加减乘。
三、圆的弧度制:
四、复数:设 为实数,,形如 的数叫复数。 为复平面的实数轴, 为虚数轴,从 到 的向量表示复数 ,其中模长为 ,幅角为以 轴正半轴到已知向量的转角的有向角。
五、单位根。(下文默认 为 的正整数次幂)
在复平面上以 为圆心, 为半径做圆。 等分后再做 个向量。则幅角为正且最小的向量对应复数 ,称为 次单位根。根据向量的乘法,其余 个复数为 。其中 。
接下来证明两个之后会用到的公式。
- 证明
- 证明
特殊的,。
正文
这里的 FFT 是要解决两个多项式相乘的问题。
如果用系数表示法直接暴力相乘,复杂度 ,很难进一步优化,所以考虑点值表示法。
点值表示法的做法为,对于两个多项式分别找出足够的点,将其对应的函数值相乘,再通过某种方式转回系数表示法。
然而,如果我们随便选 个点并求出其对应的值,单次复杂度线性,执行 次 ,还是不行。
因此我们所选的 个点必须要有很好的性质,从而减少计算量。
而 刚好有着很强的性质,可以作为所选的 个值,通过分治可将复杂度降至 。
接下来会简单介绍做法。
定义多项式 。
按下标奇偶分类后得:
定义:
那么可得:
带入 得:
带入 得:
可以发现,当 取遍 和 时, 转移项下标都取遍 。第二个式子与第一个式子只差一个符号,所以递归完后可以同时算,复杂度 。
这步操作也被称为蝴蝶操作,即将 向 和 连边, 向 和 连边,形成的图形像蝴蝶的双翅。(其实这里的连边是上标之间的连边, 接在 右边)
可能会有人问:自变量为什么必须是 ,而不能是 呢?
原因很简单:当 取遍 和 时,两段对应的区间完全不同,导致搜索量无法减半,复杂度 。
这里想明白 FFT 基本就算学明白了。
现在我们已经得到新多项式的 个点了,考虑怎么转化回系数,即 IFFT。
因为 。所以 。
定义 。
那么,
定义 。
两边同乘 得:。
两者相减得:。
所以 。
带入 得:。
当 时 。
当 时 。
所以 ,。
FFT 算法到这里就结束了。
具体实现时,可对多项式 分别跑一遍 FFT,对应函数值相乘做一遍 IFFT 还原系数。
然后我们发现当递归到最底层时,序列各个元素编号二进制反转后是连续的。
所以可以先把最底层的弄出来,自底向上合并,常数很小。
code:
#include<bits/stdc++.h>
using namespace std;
typedef double db;
const int N=1e7+5;
const db pi=acos(-1.0);
int n,m,r[N],lim=1;
struct node{db x,y;node(db x=0.0,db y=0.0):x(x),y(y){}};
node operator+(node a,node b){return node(a.x+b.x,a.y+b.y);}
node operator-(node a,node b){return node(a.x-b.x,a.y-b.y);}
node operator*(node a,node b){return node(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}
void FFT(node *a,int op){
for(int i=0;i<lim;++i)if(i<r[i])swap(a[i],a[r[i]]);
for(int t=1;t<lim;t<<=1){
node B=node(cos(pi/t),op*sin(pi/t)),mi,x,y;
for(int len=t<<1,j=0;j<lim;j+=len){
mi=node(1,0);
for(int k=0;k<t;++k,mi=mi*B){
x=a[j+k];y=mi*a[j+t+k];
a[j+k]=x+y;a[j+t+k]=x-y;
}
}
}
}
node a[N],b[N];
int main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=0;i<=n;++i)cin>>a[i].x;
for(int i=0;i<=m;++i)cin>>b[i].x;
int l=0;
while(lim<=n+m)lim<<=1,++l;
for(int i=0;i<lim;++i)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
FFT(a,1);FFT(b,1);
for(int i=0;i<lim;++i)a[i]=a[i]*b[i];
FFT(a,-1);
for(int i=0;i<=n+m;++i)cout<<(int)(a[i].x/lim+0.5)<<" ";
cout<<endl;
return 0;
}
本文作者:HQJ2007
本文链接:https://www.cnblogs.com/HQJ2007/p/17561323.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具