【luogu P3803】【模板】多项式乘法(FFT)
【模板】多项式乘法(FFT)
题目链接:luogu P3803
题目大意
给你两个多项式,要你求这两个多项式乘起来得到的多项式。(卷积)
思路
系数表示法
就是我们一般来表示一个多项式的方法:
或者可以这样表示:
那你很容易看到,用来做这道题用系数表示法来做是 的。
点值表示法
假设我们已经知道了这个多项式,那我们把 个不同的 带入,会得到 个取值 。
由于 个点可以确定一个 次多项式,那我们就可以根据这 个点确定这个多项式。
那你如果选了 个点 ,那就有:
但用它计算还是 ,你选点 ,对于每个点计算也是 。
考虑优化
至于系数表示法,它的系数固定,你一改其它都要改,似乎很难弄。
但是第二种点值法似乎没有特别大的限制让它难以优化。
但是怎么优化呢?这就要看点前置知识才可以继续了。
前置知识
向量
普通的量只有大小,但是向量就是有方向又有大小的量。
在几何里面它其实就是一个箭头。
圆的弧度制
等于半径长的圆弧所对的圆心角叫做1弧度的角,叫做弧度,用 rad 表示。
用它做单位来量角的制度就是弧度制。
复数
设 为实数,,那形如 的数就叫做复数。
就是其中的复数单位。
复数其实可以在一个二维平面表示出来, 轴表示实数 , 轴(当然在原点的时候不是虚数)表示虚数 。
然后从 到 的向量就表示了复数 。
模长:就是那个点到原点的距离,勾股就可以得到。
幅角:从 x 正半轴以逆时针为正方向到一直向量的转角。
有关复数的运算法则
那由于复数在平面上是向量,那我们其实可以用向量的加减法则来弄。(就是用那个平行四边形定则)
然后乘法就稍微复杂一点。
几何的意义就是,复数相乘,模长也相乘,然后幅角就会相加。
其实我们这里要的是代数意义,我们化一下式子:
()
单位根
在复平面上,以原点为圆心, 为半径作圆,所得的圆叫单位圆。
以圆点为起点,圆的 等分点为终点,做 个向量,设幅角为正且最小的向量对应的复数为 ,称为 次单位根。
那剩下的那 个就是 。
然后要注意的就是 和 都是 ,对于的是 正半轴的向量。
那怎么计算其它的呢?
我们可以用欧拉公式来求:
(大概就是把模长那条线往 轴做垂线得到直角三角形,然后用三角函数得到两条直角边的长度,从而得到坐标,也就是复数)
当然显而易见,单位根幅角就是
还有就是如果 ,那 就是 次单位根。
单位根的一些性质
证明:
证明:
()
那就有
OK,现在前置知识结束,开始正文。
快速傅里叶变换(FFT)
我们设 的系数为 ( 次多项式)
那就有
我们可以把它奇偶分开,
那我们可以设
那我们容易看出 。
把 带入,
把 带入,,然后把这个式子化一下:
你会发现这两个东西不同的只有一个常数项。
那我们可以枚举得到第一个答案,然后通过第一个答案得到第二个。
那你会发现你只用枚举前面的一半,那问题规模就减半。
那分出的两个问题可以继续分半解决,那我们可以用递归来搞。
那这个其实就是类似分治的感觉,是 的。
快速傅里叶逆变换(IFFT)
前面我们在弄的时候都是用点值表示法。
但一般我们(题目)给的多半都是系数表示法。
从系数表示法得到点值表示法我们已经学会了,但是怎么转回来呢?
这个时候就要用到 IFFT 了。
为 的傅里叶变换(就是点值表示)
那另外有一个 满足:
(其实就是把 当做多项式,求它在 的点值表示)
然后来推推公式:
我们设 。
带入 :
然后分类讨论:
当 时,。
那式子就变成了
当 时,我们考虑怎么求。
观察到没项都乘了一个值,我们考虑用这么一种方法:
乘 :
然后两个相减:
OK,分析完了 ,我们回到之前卡克的地方:
那只会有一个 ,使得 ,贡献是 ,其它的时候,贡献都是 。
那就是
那你就会发现,
那我们就可以通过求出 ,然后再根据这个一弄就可以得到 了。
(而且你会发现求 和求 FFT 的很像,只是由 变成了 ,那我们只要再搞一个 ,记录它单位的改变即可以了)
算法总结
它的主要思想就是把系数表示法转成点值表示法,然后用点值表示法的分支方法来快速得到答案,然后再将答案转回系数表示法。
实现
妹想到把,还有。
别的地方都没有问题,我们来讲讲递归的做法。
递归
(我一定不会告诉你这个是过不了的)
递归其实就是根据我们前面的奇偶分开做法,把一个序列弄成两个,然后递归处理之后合并。
递归到只剩一个常数项的时候,就可以直接返回。
然后我们先来一个小小的优化。
它有一个很牛逼的名字——蝴蝶效应。
(其实就是记忆化)
因为向量的乘法耗比较多时间,我们可以先把要乘的乘出来放一个变量里,然后要用的时候直接就是这个变量。
(这样如果这个乘法值要用多次的话就可以节省时间,原本要乘很多次,现在只用乘一次)
然而就算你加了这个优化,也是过不了的。
因为你递归,加上常数比较大,在 就爆了。
那我们就要找一个更优的方法来弄,那就是迭代实现。
迭代实现
我们考虑观察一下原序列和翻转之后的序列。
我们观察一下它是怎么变的:
似乎没有什么想法,转成二进制:
再把一开始的也转成二进制:
你会发现它就是把它们的二进制翻转了。
那你可以用一个 的方法得到对应关系——DP。
大概就是把之前翻转好的往左边移一个,流出位置给原序列中右边新出现的一位放。
接着就是怎么通过这个翻转对应得到我们迭代的序列。
现有一个显然的东西,就是
那也就是说,它们之间是两两对应的。
那就需要把每对都只翻转一次,就可以了。
那简单,我们只要找一个在一对里面只会发生一次的事,就比如 。(当然你小于也可以,只要让一对只有一个会发生就可以了)
然后我们来讲讲迭代要怎么搞。
其实就想到与从下段直接网上合并。(因为你已经确定了最下的序列)
就枚举合并的大小,然后枚举区间,然后就合并。
小声哔哔
终于写完了,好累啊。
awa
代码
__EOF__

本文链接:https://www.cnblogs.com/Sakura-TJH/p/luogu_P3803_FFT.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现