【luogu P4245】【模板】任意模数多项式乘法(拆系数FFT)(MTT)

【模板】任意模数多项式乘法

题目链接:luogu P4245

题目大意

给你两个多项式,要你求它们的卷积。
然后模数任意。

思路

这里用的是拆系数 FFT,即 MTT。

我们把一个数拆成 a215+b 的形式(不一定是 215,大概在 即可)

那两个数相乘:c1c2=(a1215+b1)(a2215+b2)=a1a2230+(a1b2+a2b1)215+b1b2

那我们就只用做 4 次多项式乘法就可以啦~
(然而 12 次 FFT,直接炸飞)

而且就算你一开始的 FFT 只用 4 次,也要 8 次,难以接受。

考虑搞点优化,用一些式子来优化。
(a+bi)(c+di)=acbd+(ad+bc)i
(abi)(c+di)=ac+bd+(adbc)i

会发现我们让 P=a1+a2i,P=a1a2i,Q=b1+b2i
那上面的两个式子就是 PQ,PQ

然后你考虑加在一起,就是 2(ac+adi),带入到这里就是 2(a1b1+a1b2i)
那我们不难看出除二就可以求出这两个。

然后分别用上面的两个式子减去 a1b1,a1b2 就可以得到 a2b2,a2b1 了。
(用 i 项那边减)

然后带入回 a1a2230+(a1b2+a2b1)215+b1b2 就有答案了。

然后中间搞的过程会到 1019(IDFT 之前),所以要 long double。

不难看出现在就变成了 5 次 FFT(三次 DFT,两次 IDFT)。

其实还可以预处理单位根来再次优化。

代码

#include<cmath> #include<cstdio> #include<algorithm> #define lim 32000 #define ll long long using namespace std; struct complex { long double x, y; complex (long double xx = 0, long double yy = 0) { x = xx; y = yy; } }p1[100001 << 2], p2[100001 << 2], q[100001 << 2]; int n, m, limit, l_size, an[100001 << 2]; ll mo, x, ans[100001 << 2]; long double Pi = acos(-1.0); complex operator +(complex x, complex y) { return (complex){x.x + y.x, x.y + y.y}; } complex operator -(complex x, complex y) { return (complex){x.x - y.x, x.y - y.y}; } complex operator *(complex x, complex y) { return (complex){x.x * y.x - x.y * y.y, x.x * y.y + x.y * y.x}; } void FFT(complex *now, int op) { for (int i = 0; i < limit; i++) if (an[i] > i) swap(now[i], now[an[i]]); for (int mid = 1; mid < limit; mid <<= 1) { complex Wn(cos(Pi / mid), op * sin(Pi / mid)); for (int R = (mid << 1), j = 0; j < limit; j += R) { complex w(1, 0); for (int k = 0; k < mid; k++, w = w * Wn) { complex x = now[j + k], y = w * now[j + mid + k]; now[j + k] = x + y; now[j + mid + k] = x - y; } } } } int main() { scanf("%d %d %lld", &n, &m, &mo); for (int i = 0; i <= n; i++) { scanf("%lld", &x); p1[i] = (complex){x / lim, x % lim};//拆开 p2[i] = (complex){x / lim, -x % lim}; } for (int i = 0; i <= m; i++) { scanf("%lld", &x); q[i] = (complex){x / lim, x % lim}; } limit = 1; while (limit <= n + m) { limit <<= 1; l_size++; } for (int i = 0; i < limit; i++) an[i] = (an[i >> 1] >> 1) | ((i & 1) << (l_size - 1)); FFT(p1, 1); FFT(p2, 1); FFT(q, 1); for (int i = 0; i < limit; i++) q[i].x /= limit, q[i].y /= limit;//先除了后面就不用除 for (int i = 0; i < limit; i++) p1[i] = p1[i] * q[i], p2[i] = p2[i] * q[i]; FFT(p1, -1); FFT(p2, -1); for (int i = 0; i <= n + m; i++) { ll a1b1 = (ll)floor((p1[i].x + p2[i].x) / 2 + 0.5) % mo;//两个的和除二 ll a1b2 = (ll)floor((p1[i].y + p2[i].y) / 2 + 0.5) % mo; ll a2b1 = ((ll)floor(p1[i].y + 0.5) - a1b2) % mo; ll a2b2 = ((ll)floor(p2[i].x + 0.5) - a1b1) % mo; ans[i] = (a1b1 * lim * lim + (a1b2 + a2b1) * lim + a2b2) % mo; ans[i] = (ans[i] + mo) % mo;//带进去式子里面算 printf("%lld ", ans[i]); } return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/luogu_P4245.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(57)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示