闲话 23.1.23

闲话

这一版不老不死我已经循环播放好几天了(
真的好听!洛佬 AI 加持的声线清楚!

看到评测机天体图(?)后对比了我笔记本里的 CPU
发现我这个是 up-特高性能 的 Ryzen 5600
怪不得我电脑上跑出来的速度比各 OJ 的测速快(

《付费用户专享:i9-13900KS》

快速阶乘算法

妙妙算法。首先你需要一个任意模数多项式乘法板子,然后还需要会一点拉格朗日插值。前者是为了应对奇妙模数,后者则是可以不至于多次在点值和系数间转化。

我们需要计算的是 \(n!\pmod P\)。这个东西挺朴实的,也没啥能用的好性质,那我们直接考虑分段求解。我们假设把 \(1\sim n\) 的数分成 \(B\) 长度的段,统一计算答案。最后不满的一段可以 \(O(B)\) 计算。那统一计算就是得到这一块里的积,我们可以用一个函数描述:

\[f(B, x) = \prod_{i = 1}^B (x + i) \]

假设我们能快速求得 \(f(B, 0), f(B, B), f(B, 2B), \dots, f(B, \left\lfloor\frac{n}{B}\right\rfloor B)\) 的话,我们就可以直接扫一遍这些值,在 \(O(\left\lfloor\frac{n}{B}\right\rfloor + B)\) 的复杂度内得到答案了。
其实我们可以发现 \(f(B, x)\) 是个 \(B\) 次多项式,所以我们可以简单地通过插值和多点求值在 \(O(B \log^2 B)\) 内得到答案。这个玩意最优位置不解析,所以只能硬调块长了。
有没有更快的做法?\(O(\sqrt n \log n)\) 的做法就挺好的(

我们发现,我们没必要去找到 \(f(B, x)\) 的系数,而是可以直接用点值求解点值。我们需要的是 \(d = B\) 时的 \(f(d, 0), f(d, B), f(d, 2B), \dots, f(d, dB)\),这启发我们在 \(d\) 一维上倍增。
由于最终我们只能得到 \(f(B, B^2)\),取 \(B = \left\lfloor\sqrt n\right\rfloor\)

记数列 \(f(d, 0), f(d, B), f(d, 2B), \dots, f(d, dB)\)\(\langle f(d, d) \rangle\)

我们要想倍增 \(d\),需要的就是 \(\langle f(d, d) \rangle \to \langle f(2d, 2d) \rangle\)\(\langle f(d, d) \rangle \to \langle f(d + 1, d + 1) \rangle\) 两个操作。这两个操作的复杂度不能超过 \(O(\sqrt n\log n)\),这样能得到总时间复杂度 \(O(\sqrt n\log n)\)
分别讨论。

\(\langle f(d, d) \rangle \to \langle f(d + 1, d + 1) \rangle\)

直接扫就行了。前 \(d\) 个直接乘 \((x + d + 1)\),新的一个直接朴素计算即可。单次复杂度 \(O(d)\) 的。

\(\langle f(d, d) \rangle \to \langle f(2d, 2d) \rangle\)

我们只需要求得 \(\langle f(d, 2d)\rangle\)\(f(d, d), f(d, B + d), f(d, 2B + d), \dots, f(d, 2dB + d)\),就能按位相乘得到 \(f(2d, 2d)\) 了。
不妨构造 \(h(x) = f(d, Bx)\),这样我们这两个操作就可以刻画成用 \(h\) 的一些点值得到另一些点值的操作,即给定 \(h(0), h(1), h(2),\dots, h(k)\) 且保证 \(k \ge d\),求 \(h(\Delta + 0), h(\Delta + 1), h(\Delta + 2), \dots, h(\Delta + k)\)

然后可以转化上面的问题:
从点值序列 \(h(0), h(1), h(2), \dots, h(d)\) 得到 \(h(d + 1 + 0), h(d + 1 + 1), h(d + 1 + 2), h(d + 1 + d)\)
\(h(0), h(1), h(2), \dots, h(2d)\) 得到 \(h(d/B + 0), h(d/B + 1), h(d/B + 2), \dots, h(d/B + 2d)\)

这就是 P5667 的形式。尝试应用拉格朗日插值算法,我们能写出任意点的值即

\[\begin{aligned} h(\Delta + n) &= \sum_{i = 0}^k h(i)\prod_{j \neq i} \frac{\Delta + n - j}{i - j} \\ &= \left(\prod_{j = 0}^k\Delta + n - j\right)\left( \sum_{i = 0}^k \frac{h(i)}{\Delta + n - i}\prod_{j\neq i}\frac{1}{i - j} \right) \\ &= \left(\prod_{j = 0}^k\Delta + n - j\right)\left( \sum_{i = 0}^k \frac{h(i)}{\Delta + n - i}\frac{1}{(-1)^{k - i}i!(k - i)!} \right) \end{aligned}\]

我们直接把左边括号里的阶乘除过去,然后右边括号里的东西是可以卷积得到的。具体地,如果我们向右平移序列 \(n\) 位,让第 \(i + n\) 位计算 \(h(\Delta + n)\) 的值,则可以构造

\[f(i) = \frac{h(i)}{(-1)^{k - i}i! (k - i)!} \qquad g(j) = \frac{1}{\Delta - k + n} \]

则有

\[h(\Delta - k + n) = \left(\prod_{j = 0}^k\Delta + n - j\right) \sum_{i + j + k} f(i) g(j) \]

直接做就行了,单次复杂度 \(O(d \log d)\) 的。

因此我们就可以做到总时间复杂度 \(O(\sqrt n \log n)\) 计算原问题。

Submission.

posted @ 2023-01-23 20:09  joke3579  阅读(83)  评论(1编辑  收藏  举报