多项式大总结
文章没有写完,近期填完这坑
参考文章:
https://www.luogu.com.cn/blog/froggy/duo-xiang-shi-tai-za-hui
https://www.cnblogs.com/zwfymqz/p/8244902.html
https://www.cnblogs.com/RabbitHu/p/FFT.html
https://blog.csdn.net/enjoy_pascal/article/details/81478582
Thomas H.Cormen,Charles E.Leiserson,Ronald L.Rivest,Clifford Stein. 殷建平等译. 算法导论第三版 [M]. 北京:机械工业出版社,2013,527-542.
多项式的定义:
一个
其中
拉格朗日插值法:
给定
定理:
不会证明这个。
代码:
int n; ll x[N], y[N], k; ll ans; ll qpow(ll a, ll b) { ll ans = 1; for (; b; b >>= 1, a = a * a % mod) if (b & 1) ans = ans * a % mod; return ans; } int main() { scanf ("%d%lld", &n, &k); for (int i = 1; i <= n; i++) scanf ("%lld%lld", &x[i], &y[i]); for (int i = 1; i <= n; i++) { ll tmp = y[i]; for (int j = 1; j <= n; j++) if(i != j) tmp = tmp * ((k - x[j] + mod) % mod) % mod * qpow((x[i] - x[j] + mod) % mod, mod - 2) % mod; ans = (ans + tmp) % mod; } printf ("%lld\n", ans); return 0; }
快速傅里叶变换 FFT:
为了方便计算,这里的
在此之前,先明白 DFT(离散傅里叶变换)、FFT(快速傅里叶变换)、IDFT(离散傅里叶逆变换)、IFFT(快速傅里叶逆变换) 之间的关系:
类似的,IDFT 也是概念,IFFT 是基于 IDFT 概念的算法。现在不知道这四个东西的具体含义在下文介绍。
给定两个多项式
这里定义多项式卷积运算:
然后快速傅里叶变换可以在
多项式的点值表示:
点值表示法,顾名思义就是把用
而点值表示法的优点就是可以在
多项式的系数表示:
系数表示法,就是记录
那么我们可以推测,多项式乘法流程一般是:系数表示法先转化到点值表示法,这样就能快速卷积,然后由转回系数表示法。也就是有两步,第一步系数转点值就是 DFT;第二步点值转系数 IDFT。
复数:
介绍:
提示:建议学习数学选修 2-2 的复数相关课程。
;- 实数可以与它进行四则运算,进行四则运算时,原有的加法和乘法运算律仍然成立。
有一个复数
如图点
或者,你还可以把复数作成一个向量:
向量
共轭复数:
一般地,当两个复数的实部相等,虚部互为相反数时,这两个复数叫做互为共轭复数。通常记复数
比如当
复数的运算:
设
其中,复数相乘时,模长相乘,幅角相加。
DFT:
其实 DFT 的本质就是代入
单位根:
在复平面中,以原点为圆心,半径为
这几个复数就是那几个 “特殊的复数”。
单位根的性质:
性质一:
证明:
性质二:
证明:
FFT 的流程:
朴素的 DFT 是
设多项式
设:
那么:
将
将
然后我们就可以通过
IFFT 的流程:
我们得到了点值表示
关于证明,stoorz 爷的看法:
QuantAsk 爷的看法:
虽然二位爷都对我进行嘲讽,但这不影响我们证明。
将
发现最后的和式是等比数列求和。设
而
代回原式:
所以 IFFT 只用用对点值表示的
蝴蝶变换优化:
递归 FFT 常数大,所以尝试把它变成迭代形式。
按奇偶性划分可以发现一个规律:
十进制 | 二进制 |
---|---|
每一个数最终的位置,就是把它的二进制翻转过来。
所以每个数的位置就能够 tr[i]
)。
代码:
const int N = 1500010; const double PI = acos(-1.0); struct Complex { double x, y; Complex (double ix, double iy) {x = ix, y = iy;} Complex () {x = y = 0;} Complex operator + (Complex &b) { return Complex(x + b.x, y + b.y); } Complex operator - (Complex &b) { return Complex(x - b.x, y - b.y); } Complex operator * (Complex &b) { return Complex(x * b.x - y * b.y, x * b.y + y * b.x); } }f[N << 1], g[N << 1]; int n, m; int tr[N << 1]; void FFT (Complex *f, bool isDFT) { for (int i = 1; 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; Complex omega(cos(2 * PI / p), sin(2 * PI / p)); if (!isDFT) omega.y *= -1; //共轭复数 for (int k = 0; k < n; k += p) { Complex tmp(1, 0); for (int i = k; i < k + len; i ++) { Complex y = tmp * f[i + len]; f[i + len] = f[i] - y; f[i] = f[i] + y; tmp = tmp * omega; } } } if(!isDFT) for (int i = 0; i <= n; i++) f[i].x /= n; } int main() { scanf ("%d%d", &n, &m); for (int i = 0; i <= n; i++) scanf ("%lf", &f[i].x); for (int i = 0; i <= m; i++) scanf ("%lf", &g[i].x); for (m += n, n = 1; n <= m; n <<= 1); for (int i = 1; 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++) printf ("%d ", (int)(f[i].x + 0.49)); return 0; }
快速数论变换 NTT:
由于 FFT 会有很大的精度误差,所以用模数操作代替,就是 NTT 了,NTT 也比 FFT 要快很多。
原根的定义:
百度百科给的定义:
原根是一种数学符号,设是正整数, 是整数,若 模 的阶等于 ,则称 为模 的一个原根。
简单点说:
若整数
在模
NTT 的基本流程:
若
但 NTT 也有局限性,只能处理
代码:
const int N = 1500010; const ll mod = 998244353ll, G = 3, invG = 332748118ll; ll f[N << 1], g[N << 1]; int n, m; int tr[N << 1]; ll qpow(ll a, ll b) { ll ans = 1; for (; b; b >>= 1, a = a * a % mod) ans = ans * (b & 1? a: 1) % mod; return ans; } void NTT (ll *f, bool isDFT) { for (int i = 1; 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; ll omega = qpow(isDFT? G: invG, (mod - 1) / p); for (int k = 0; k < n; k += p) { ll tmp = 1ll; for (int i = k; i < k + len; i ++) { ll y = tmp * f[i + len] % mod; f[i + len] = (f[i] - y + mod) % mod; f[i] = (f[i] + y) % mod; tmp = tmp * omega % mod; } } } if(!isDFT) { ll invn = qpow(n, mod - 2); for (int i = 0; i <= n; i++) f[i] = f[i] * invn % mod; } } int main() { scanf ("%d%d", &n, &m); for (int i = 0; i <= n; i++) scanf ("%lld", &f[i]); for (int i = 0; i <= m; i++) scanf ("%lld", &g[i]); for (m += n, n = 1; n <= m; n <<= 1); for (int i = 1; i <= n; i++) tr[i] = (tr[i >> 1] >> 1) | (i & 1? n >> 1: 0); NTT(f, 1), NTT(g, 1); for (int i = 0; i <= n; i++) f[i] = f[i] * g[i]; NTT(f, 0); for (int i = 0; i <= m; i++) printf ("%lld ", f[i]); return 0; }
多项式乘法逆:
给定一个多项式
思路:
运用倍增的思想求解。
设已经求出
式子两边同时取平方:
式子两边同时乘
接着就可以套 NTT 求解了。
代码:
const int N = 1500010; const ll mod = 998244353ll, G = 3, invG = 332748118ll; ll f[N << 1], g[N << 1]; int n, m; int tr[N << 1]; ll qpow(ll a, ll b) { ll ans = 1; for (; b; b >>= 1, a = a * a % mod) ans = ans * (b & 1? a: 1) % mod; return ans; } void NTT (ll *f, bool isDFT, int n) { for (int i = 1; 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; ll omega = qpow(isDFT? G: invG, (mod - 1) / p); for (int k = 0; k < n; k += p) { ll tmp = 1ll; for (int i = k; i < k + len; i ++) { ll y = tmp * f[i + len] % mod; f[i + len] = (f[i] - y + mod) % mod; f[i] = (f[i] + y) % mod; tmp = tmp * omega % mod; } } } if(!isDFT) { ll invn = qpow(n, mod - 2); for (int i = 0; i <= n; i++) f[i] = f[i] * invn % mod; } } ll h[N << 1]; void Inv(ll *f, ll *g, int m) { if (m == 1) { g[0] = qpow(f[0], mod - 2); return ; } Inv(f, g, (m + 1) / 2); int n; for (n = 1; n < (m << 1); n <<= 1); for (int i = 0; i <= n; i++) tr[i] = (tr[i >> 1] >> 1) | (i & 1? n >> 1: 0), h[i] = f[i] * (i <= m); NTT(h, 1, n), NTT(g, 1, n); for (int i = 0; i <= n; i++) g[i] = (2 - g[i] * h[i] % mod + mod) % mod * g[i] % mod; NTT(g, 0, n); for (int i = m; i <= n; i++) g[i] = 0; } int main() { scanf ("%d", &n); for (int i = 0; i < n; i++) scanf ("%lld", &f[i]); Inv(f, g, n); for (int i = 0; i < n; i++) printf ("%lld ", g[i]); return 0; }
多项式对数函数:
给定一个多项式
求导:
提示:建议学习数学选修 2-2 的导数相关课程。
瞬时变化率:
先咕到这罢!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
· [翻译] 为什么 Tracebit 用 C# 开发
· 腾讯ima接入deepseek-r1,借用别人脑子用用成真了~
· Deepseek官网太卡,教你白嫖阿里云的Deepseek-R1满血版
· DeepSeek崛起:程序员“饭碗”被抢,还是职业进化新起点?
· 深度对比:PostgreSQL 和 SQL Server 在统计信息维护中的关键差异