多项式操作汇总
多项式操作汇总
最近学了不少多项式的小 trick,感觉不记一下容易忘。那我就把做法都摆在这里吧,代码还是自己去写了。(鉴于所有操作都是建立在 FFT 之上的,所以会写 FFT 的话所有东西的代码都能写出来了)
多项式乘法
这个就是一切的基础,先把这个代码搞熟后面就都能写啦。
多项式逆元
定义多项式 \(g(x)\) 的逆元 \(g^{-1}(x)\)(以下为了方便简记为 \(f(x)\))是满足如下等式的多项式:
可以想象,模 \(x^n\) 的意思就是把 \(x^{n+1}\) 及更高的项都删掉,剩下的部分。为什么我们可以这样做呢?因为在解决实际问题的时候我们都只关心前 \(n\) 项(即 \(x^0 \sim x^{n-1}\) 的系数)。
求 \(f(x)\) 的方法可能不止一种,但是我觉得用泰勒展开这个方法虽然麻烦但是好记(因为大多数问题可以用它——它是“通解”)。
注意:以下多项式都会在第一次用 \(f(x)\) 表示过后就可能省掉后面的 \((x)\),你需要知道它是一个多项式。
我们先构造一个函数 \(H(f) = f^{-1} - g\)。你没有看错,这个 \(H(f)\) 函数的自变量就是一个多项式。为什么要这样构造函数呢?因为可以发现当自变量 \(f = g^{-1}\),即 \(f\) 就是答案的时候,\(H(f) = 0\)。
我们用倍增的方法求这个 \(f\),假设已经求出 \(f_0(x)\) 是 \(f\) 前 \(\lceil \frac{n}{2} \rceil\) 项,即
现在我们要用 \(f_0\) 和 \(g\) 表示出 \(f\)。利用我们刚才构造的函数,根据 \(f_0\) 的定义有
为了实现上面黑体字的目的,我们对 \(H(f)\) 在 \(f_0\) 处进行泰勒展开(想点进去看的请翻到文章末尾),即用下面这个式子去逼近 \(H(f)\)
注意上面这个等式不是模意义下的,不需要取模它们就相等。
接下来我们观察一下 \((f-f_0)^i\),由于 \(f - f_0 \equiv 0 (\mod x^{\lceil \frac{n}{2} \rceil})\),发现当 \(i = 2\) 时,最高次项平方之后次数一定 \(\ge n\),所以有 \(\forall i > 1, (f-f_0)^i \equiv 0 (\mod x^n)\),那么上面的泰勒展开的无穷多项就只剩了两项,整理出来就是
移项得到
至此我们得到了牛顿迭代的公式,以后可以记它,就不用每次都泰勒展开了。接下来的工作就是把刚才构造的 \(H(f)\) 带进去。
递归下去,再用这个式子回溯,就求出来啦!
时间复杂度:\(T(n) = T(\frac{n}{2}) + O(n\log n) = O(n\log n)\),发现这个小秘密后,我们可以无限套,理论复杂度不变
多项式除法 & 取模
注意这里的除法不是在模意义下的!所以它会有商和余数,即如果我要求 \(\frac{A(x)}{B(x)}\),我就是要求两个多项式 \(D(x)\) 和 \(R(x)\),使得它们满足下面这样一个等式
现在我们记 \(n\) 为 \(A\) 的次数,\(m\) 为 \(B\) 的次数,那么有 \(D\) 的次数为 \(\mathrm{max}(n - m, 0)\),\(R\) 的次数 \(\le m - 1\)。
记 \(F(x)\) 的次数为 \(t\), \(F^R(x) = F(\frac{1}{x}) \cdot x^t\),直观理解就是把 \(F\) 的系数反转一下。
接下来将 \(\frac{1}{x}\) 带入到原式中,再两边同乘 \(x^n\),看看会发生什么
如果我们将新的等式放在模 \(x^{n-m+1}\) 的意义下,\(R(x)\) 就被消掉了!与此同时 \(D^R(x)\) 最高次为 \(x^{n-m}\),故不会受到任何影响,即模意义下求出的 \(D^R(x)\) 与非模意义下的完全相同。
\(D(x)\) 就是 \(D^R(x)\) 反转一下系数,把 \(D(x)\) 代到最初的式子里就能得到 \(R(x)\) 了。
多项式多点求值
任务:给出多项式 \(F(x)\) 和集合 \(\{ x_1, x_2, \cdots , x_n \}\),要求返回一个集合 \(\{ F(x_1), F(x_2), \cdots , F(x_n) \}\)。
构造多项式 \(A(x) = (x - x_1)(x - x_2)\cdots(x - x_n)\),令 \(G(x) \equiv F(x) (\mod A(x))\),那么对于 \(\forall i \in [1, n], G(x_i) = F(x_i)\)。
为什么呢?因为
而 \(\forall i \in [1, n], A(x_i) = 0, \therefore F(x_i) = G(x_i)\)。
于是就可以分治了。令 \(A_l(x) = \prod_{i = 1}^{\lfloor \frac{n}{2} \rfloor} { (x - x_i) }, A_r(x) = \prod_{i = \lfloor \frac{n}{2} \rfloor + 1}^n { (x - x_i) }\),然后把 \(F(x)\ \mod A_l(x)\) 和 \(F(x)\ \mod A_r(x)\) 扔下去递归了。
时间复杂度:\(O(n \log^2 n)\)
多项式 ln & exp
应该有不少人好奇这两个操作的组合意义是什么。事实上这个问题我并不是很清楚,我们也许可以通过泰勒展开后的系数研究一下它的组合意义,但它们的价值主要不在于组合意义,而在于其他方面的应用。
具体应用在讲完做法后立刻就会有一个,所以不必着急,我们先看看多项式取 ln 怎么求。
多项式取 ln
注意到这样一个等式
于是积分一下就可以轻易地求出 \(\ln f(x)\) 了
其中,求导和积分都能在 \(O(n)\) 时间内求出,所以总时间复杂度 \(O(n)\)。
细心的读者一定会问:积分之后,常数项如何确定?注意到这里的 \([x^0]f(x)\) 必须为 \(1\),也就是说 \([x^0] \ln f(x)\) 一定是 \(0\),那么积分完后常数项为 \(0\) 就好了。(如果 \([x^0]f(x) \ne 1\) 怎么求?QAQ 我也不会,因为我们似乎很难找到 \(\ln c (c > 0)\) 在剩余系下的表示……)
多项式求 exp
我们要求这样的一个函数 \(f(x)\),满足
\begin{matrix}
f(x) \equiv e^{g(x)} & (\mod x^n)
\end{matrix}
两边取对数,我们有
\begin{matrix}
\ln f(x) \equiv g(x) & (\mod x^n)
\end{matrix}
我们还是考虑倍增求 \(f\),套用上面泰勒展开推出来的结果,构造函数 \(H(f) = \ln f - g\),令 \(f_0 \equiv f (\mod x^{\lceil \frac{n}{2} \rceil})\),于是得到
\begin{matrix}
f \equiv f_0 - \frac{\ln f_0 - g}{f_0^{-1}} & (\mod x^n) \\
f \equiv f_0(1 - \ln f_0 + g) & (\mod x^n)
\end{matrix}
这样就 \(O(n \log n)\) 解决啦,你会发现求 exp 其实依赖于求 ln。同样地我们只能求 \([x^0]g(x) = 0\) 的 \(e^{g(x)}\)。
接下来就是一个经典的应用。
多项式乘方
求 \(f(x)\) 满足
\begin{matrix}
f(x) \equiv g(x) ^ k & (\mod x^n)
\end{matrix}
指数相关,考虑两边取对数
\begin{matrix}
\ln f(x) \equiv k \ln g(x) & (\mod x^n) \\
f(x) = e^{k \ln g(x)} & (\mod x^n)
\end{matrix}
这就解决了?别忘了常数项的问题,问题有两个:
- \([x^0]g(x) > 1\),我们可以将常数项提出来,得到 \(g'(x)\)(注意不是导数,这个就是 \(g(x)\) 的每项乘上常数项的逆元),那么问题变成了求 \((g'(x))^k c^k\),\(c\) 代表常数项,就是在上面的过程进行完后每项乘上 \(c^k\) 即可;
- \([x^0]g(x) = 0\),先位移到常数项不为 \(0\),即得到 \(g'(x) = \frac{g(x)}{x^d}\),\(d\) 表示前缀连续 \(0\) 的个数,那么问题变成了求 \((g'(x))^k x^{kd}\),就是上面的过程进行完后再位移 \(kd\) 位即可。
至此多项式乘方就完整地解决了。