当前位置: 首页 > news >正文

(笔记)多项式基础 FFT

多项式

\[F(x)=\sum_{i=0}^{i-1}a_ix^i \]

对多项式进行乘法,就是对两个多项式进行加法卷积。其中卷积结果 \(C(k)=\sum_{i=0}^kA(i)B(k-i)\)

分治乘法

\(A(x)\) 左右拆半,不足则末尾(最高位)补上 \(0\),令 \(n=2^k\)。则

\[A(x)=A_0(x)+x^{n/2}A_1(x) \]

\[A_0(x)=\sum_{i=0}^{n/2-1}a_ix^i,A_1(x)=\sum_{i=n/2}^na_ix^{i-n/2}=\sum_{i=0}^{n/2-1}a_{i+n/2}x^i \]

同理,\(B(x)\) 左右拆半,则卷积

\[\begin{aligned} AB&=(A_0+x^{n/2}A_1)(B_0+x^{n/2}B_1)\\ &=A_0B_0+x^nA_1B_1+x^{n/2}(A_0B_1+A_1B_0)\\ Q_1&=A_0B_0,Q2=A_1B_1,Q3=(A_0+A_1)(B_0+B_1)\\ AB&=Q_1+x^nQ_2+x^{n/2}(Q_3-Q_1-Q_2) \end{aligned} \]

这样可以转化为 \(3\) 个规模为 \(n/2\) 的子问题,总时间为 \(T(n)=O(n)+3T(n/2),T(n)=O(n^{\log_2 3})=O(n^{1.585})\)

点值与插值

点值:对于一个 \(x\)\(F(x)\) 的值。

插值:已知 \(F(x)\) 的若干点值,求其系数序列 \(G(x)\)

根据定义,\(F(x)=\sum_{i=0}^{n-1}x^iG(i)\)

(简记)拉格朗日插值

FFT 中的插值使用单位根反演实现。

复平面单位根

\(z=\cos\theta+i\sin\theta,\omega_n^k=\cos(\frac{2\pi k}{n})+i\sin(\frac{2\pi k}{n})\),其点集为单位圆均分成 \(n\) 份的点集,且点集包含 \(1\)

  • 性质 1\(\omega_n^k=\omega_n^{k+n}\)

  • 性质 2\(\omega_{nb}^{kb}=\omega_n^k\)

  • 性质 3:若 \(n\) 为偶数,\(\omega_n^{k+n/2}=-\omega_n^k\)

FFT

DFT

FFT 多项式乘法外层过程如下:

  • 选定 \(n(n=2^{k'})\) 个数 \(\omega_n,\omega_n^{2},...\omega_n^{n-1}\)
  • 对于 \(i\in [0,n-1]\) 求出点值 \(A(\omega_n^i),B(\omega_n^i)\)(DFT)
  • 根据 \(C(\omega_n^i)=A(\omega_n^i)B(\omega_n^i)\) 求出 \(C\) 的点值。
  • 插值求出 \(C(x)\) 的系数。(IDFT,即 DFT 逆过程)

类似分治乘法,我们采用拆半,但是是奇偶拆半。

\[\begin{aligned} F(x)&=FL(x^2)+xFR(x^2)\\ FL(x)&=\sum_{i\bmod 2=0,i\in[0,n-2]}a_ix^{i/2}\\ FR(x)&=\sum_{i\bmod 2=1,i\in[1,n-1 ]}a_ix^{(i-1)/2}\\ \end{aligned} \]

假设我们已经知道了一堆点值,代入 \(\omega_n^k,k\in[0,\frac{n}{2})\)

\[\begin{aligned} F(\omega_n^k)&=FL(\omega_n^{2k})+\omega_n^kFR(\omega_n^{2k})\\ &=FL(\omega_{n/2}^{k})+\omega_{n}^{k}FR(\omega_{n/2}^k) \end{aligned} \]

考虑到 \(FL,FR\) 的计算,我们每次实际分治时只需要计算所有 \(k<\frac{n}{2}\) 的部分,\(\frac{n}{2}\le k<n\) 的部分根据性质 1 \(\omega_{n/2}^{k+n/2}=\omega_{n/2}^k\),用到的 \(FL,FR\) 是和前面一样的,但是根据性质 3,我们在转移式子中用到的 \(\omega_n^{k+n/2}\) 需要变成 \(-\omega_n^k\),即:

\[F(\omega_n^{k+n/2})=FL(\omega_{n/2}^{k})-\omega_n^kFR(\omega_{n/2}^{k}) \]

这是 DFT 的部分。

IDFT

根据上面的 DFT:

\[F(k)=\sum_{i=0}^{n-1}(\omega_{n}^{k})^iG(i) \]

根据单位根反演:

\[n\times G(k)=\sum_{i=0}^{n-1}(\omega_{n}^{-k})^iF(i) \]

所以求出点值以后,我们再跑一边 DFT 即可。

这样我们就可以用子问题求出原问题的答案了,时间复杂度 \(T(n)=T(n/2)+O(n),T(n)=O(n\log n)\)

一些常数优化

迭代版神秘常数问题

  • 如果去掉分治过程直接迭代,先枚举起始点再枚举增量,否则会变得异常缓慢。

蝴蝶变换

  • 为了避免大规模数组拷贝,我们使用蝴蝶变换。注意到分治的过程中我们先不断地按照奇偶把数组切分然后分到两边,为了避免这个切分我们考虑一开始就把点放到它最下面那层分治所处的位置,然后合并和之前一样左边作为 \(FL\),右边作为 \(FR\)

    我们发现一个点 \(i\) 的二进制形式决定了其最终所处位置,把 \(i\) 视为 \(n=2^{k'}\)\(k'\) 位二进制数,那么 \(i\in[0,n-1]\),从上至下每次奇偶分类相当于看二进制最低位,\(0\) 就分到左边,\(1\) 就分到右边,然后右移一位。不妨把这个二进制数 reverse(翻转)一下,那么每次的选择及其权重和就恰好对应了翻转后的二进制数。于是预处理 \(i\)reverse \(tr[i]\),然后若 \(i<tr[i]\) 交换即可得到最下层位置(避免两次交换)。

三次变两次优化

构造出:

\[P(x)=F(x)+iG(x) \]

\[P(x)^2=F(x)^2-G(x)^2+2iF(x)G(x) \]

即把 \(F(x)\)\(iG(x)\) 系数相加得到 \(P(x)\) 系数后做一次 DFT,得到的点值平方后做 IDFT,最后得到的系数虚部 \(/2\) 就是 \(F(x)G(x)\)double 运算容易掉精度,承受能力为跨度上线 \(10^{12}\) 左右,如果相乘系数差太大(如一个 \(\in[10^{-6},10^{-5}]\),一个 \([10^5,10^6]\))在有平方项会导致 \(10^{24}\) 的精度跨度存在。解决办法就是乘上一个定值,做完 FFT 再除回去即可。

Code

分治版

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const double Pi=acos(-1);
const int N=1.35e6+5;
int n,m,tr[N<<1];
struct CP{CP(double xx=0,double yy=0){x=xx,y=yy;}double x,y;CP operator +(const CP &b)const{return (CP){x+b.x,y+b.y};}CP operator -(const CP &b)const{return (CP){x-b.x,y-b.y};}CP operator *(const CP &b)const{return (CP){x*b.x-y*b.y,x*b.y+y*b.x};}
}f[N<<1],g[N<<1];
void FFT(CP *f,int len,bool tf){if(len==1)return ;FFT(f,len/2,tf);FFT(f+len/2,len/2,tf);CP per(cos(2*Pi/len),sin(2*Pi/len)),now(1,0);if(!tf)per.y=-per.y;for(int k=0;k<len/2;k++){CP tt=now*f[k+len/2];f[k+len/2]=f[k]-tt;f[k]=f[k]+tt;now=now*per;}
}
int main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n>>m;for(int i=0;i<=n;i++)cin>>f[i].x;for(int i=0;i<=m;i++)cin>>g[i].x;m=m+n;for(n=1;n<=m;n<<=1);for(int i=0;i<n;i++)tr[i]=(tr[i>>1]>>1)|((i&1)?(n>>1):0);for(int i=0;i<n;i++)if(i<tr[i])swap(f[i],f[tr[i]]),swap(g[i],g[tr[i]]);FFT(f,n,1);FFT(g,n,1);for(int i=0;i<n;i++)f[i]=f[i]*g[i];for(int i=0;i<n;i++)if(i<tr[i])swap(f[i],f[tr[i]]);FFT(f,n,0);for(int i=0;i<=m;i++)cout<<(int)(f[i].x/n+0.49)<<' ';return 0;
}

迭代版

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const double Pi=acos(-1);
const int N=1.35e6+5;
int n,m,tr[N<<1];
struct CP{CP(double xx=0,double yy=0){x=xx,y=yy;}double x,y;CP operator +(const CP &b)const{return (CP){x+b.x,y+b.y};}CP operator -(const CP &b)const{return (CP){x-b.x,y-b.y};}CP operator *(const CP &b)const{return (CP){x*b.x-y*b.y,x*b.y+y*b.x};}
}f[N<<1],g[N<<1];
void FFT(CP *f,bool tf){if(n==1)return ;for(int i=0;i<n;i++)if(i<tr[i])swap(f[i],f[tr[i]]);for(int p=2;p<=n;p<<=1){int len=p>>1;CP per(cos(2*Pi/p),sin(2*Pi/p));if(!tf)per.y=-per.y;for(int l=0;l<n;l+=p){CP now(1,0);for(int k=l;k<l+len;k++){CP tt=now*f[k+len];f[k+len]=f[k]-tt;f[k]=f[k]+tt;now=now*per;}}}
}
int main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n>>m;for(int i=0;i<=n;i++)cin>>f[i].x;for(int i=0;i<=m;i++)cin>>g[i].x;m=m+n;for(n=1;n<=m;n<<=1);for(int i=0;i<n;i++)tr[i]=(tr[i>>1]>>1)|((i&1)?(n>>1):0);FFT(f,1);FFT(g,1);for(int i=0;i<n;i++)f[i]=f[i]*g[i];FFT(f,0);for(int i=0;i<=m;i++)cout<<(int)(f[i].x/n+0.49)<<' ';return 0;
}
http://www.wxhsa.cn/company.asp?id=2054

相关文章:

  • MAC tomcat启动报错
  • 研究生-必看-倒计时3天/武汉科技大学主办/稳定EI会议/高层次教授出席报告
  • LGP7113 [NOIP 2020] 排水系统 学习笔记
  • MySqlException: Incorrect string value: \xE6\x99\xBA\xE8\x83\xBD... for column FieldName at row 1
  • Burp Suite Professional 2025.9 发布 - Web 应用安全、测试和扫描
  • SQL Server 2022 RTM 累积更新 #21 发布
  • 针对WPF的功耗优化(节能编程)
  • Docker 清理完整指南:释放磁盘空间的最佳实践 - 详解
  • 微算法科技(NASDAQ: MLGO)开发Rollup技术,探索区块链扩展性解决方案
  • 征稿倒计时3天/武汉科技大学主办/医学人工智能/现可享优惠
  • 生成更智能,调试更轻松,SLS SQL Copilot 焕新登场!
  • NOI linux使用教程
  • springboot 文件处理框架
  • Docker:龙晰系统(Anolis)更新yum源下载docker
  • 针对单输入单输出、多输入多输出及三阶系统带约束的模型预测控制的实现
  • vue3中父子组件数据同步的默认方式update:xxx
  • 解决 C# 当另一个read操作挂起时不能调用read方法的问题
  • AI辅助编程_工具和方式
  • [完结10章]Java大模型工程能力必修课,LangChain4j 入门到实践
  • k8s源码分析——kubectl命令行交互
  • 将 seata 2.5 发布到私服
  • 一些感悟
  • 五款免费低代码平台深度横评:斑斑、简道云、宜搭、氚云、织信如何选?
  • ubuntu历史版本下载
  • 读书笔记:数据库索引的智能优化:反向键与降序索引
  • 代码随想录算法训练营第十天| 232.用栈实现队列、 225. 用队列实现栈、20. 有效的括号 、1047. 删除字符串中的所有相邻重复项
  • 零成本搭建企业系统:五款免费低代码平台推荐
  • 故障处理:access$表在数据库丢失的恢复
  • 从需求出发:教你判断选斑斑还是织信
  • PLC结构化文本设计模式——建造者模式(Builder Pattern)