数学杂谈 #5

多项式多点求值

传统做法,原理是:

\[\begin{aligned} x^n &\equiv ax^{n-1}\pmod {x-a}\\ &\equiv a^2x^{n-1}\pmod {x-a}\\ &\dots\\ &\equiv a^n\pmod {x-a} \end{aligned} \]

所以对于要求值的多项式 \(F(x)\) 和给定的点 \(x_1,x_2,\dots x_m\) ,我们就相当于求 \(F(x)\bmod (x-x_1),\dots,F(x)\bmod (x-x_m)\)

直接求当然不行,但是如果我们求 \(F(x)\bmod (x-a)\) ,我们先将 \(F(x)\) 模上一个包含 \((x-a)\) 因式的多项式 \(P(x)\) ,则 \((F(x)\bmod P(x))\equiv F(x)\pmod {x-a}\)

这提示我们可以逐步降低 \(F\) 的次数来优化复杂度。采用分治策略,记 \(P_{l,r}(x)=\prod_{i=l}^r(x-x_i)\) ,且 \(M=\lfloor\frac{l+r}2\rfloor\) 。如果我们得到了 \(F(x)\bmod P_{l,r}(x)\) ,我们可以让余式分别模 \(P_{l,M}\)\(P_{M+1,r}\) ,从而可以继续递归。

\(P\) 需要最初分治求并且存下来。

可以得到时间复杂度为 \(O(m\log_2^2m)\) ,由于频繁使用多项式取模,该算法拥有较大的常数。


船新做法:[1]

普通的和卷积为 \(h_k=\sum_{i+j=k}f_ig_j\) ,我们用 \(H=F\cdot G\)\(H(x)=F(x)\cdot G(x)\) 来表示。

现在我们定义差卷积为 \(h_k=\sum_{i-j=k}f_ig_j\) ,我们用 \(H=F\times G\)\(H(x)=F(x)\times G(x)\) 来表示[2]

这个做法的原理是:

\[[x^0]F(x)\times \frac{1}{1-ax}=F(a) \]

自行展开即可得知。

除此之外,差卷积还有重要的性质:

\[F\times (G\cdot H)=F\times G\times H \]

按照定义展开并整理即可得证。

因此,我们就相当于求 \([x^0]F(x)\times \frac{1}{1-x_1x},\dots,[x^0]F(x)\times \frac{1}{1-x_mx}\)

同样采取分治策略。记 \(Q_{l,r}(x)=\prod_{i=l}^r(1-x_ix)\) ,且 \(M=\lfloor\frac{l+r}2\rfloor\) 。如果我们得到了 \(F(x)\times \frac{1}{Q_{l,r}(x)}\) ,我们就乘上 \(Q_{M+1,r}(x)\) 并进入左子区间递归;右子区间类似。

由于我们最终只需要常数项的值,因此我们可以控制多项式长度为区间长度,这样就保证了时间复杂度。

\(Q\) 同样需要最初分治求并且存下来。递归根部的 \(F(x)\times \frac{1}{Q_{1,m}(x)}\) 需要求逆。

那么时间复杂度为 \(O(m\log_2^2m)\) ,不过常数会小一些,并且也会好写一些。

多项式快速插值

传统做法,原理是拉格朗日插值:

\[f(x)=\sum_{i=1}^n\frac{y_i\prod_{j\not=i}(x-x_j)}{\prod_{j\not=i}(x_i-x_j)} \]

我们只需要快速展开它即可。

将式子变形:

\[f(x)=\sum_{i=1}^n\left(\frac{y_i}{\prod_{j\not=i}(x_i-x_j)}\cdot\prod_{j\not=i}(x-x_j)\right) \]

首先处理 \(\prod_{j\not=i}(x_i-x_j)\) 。设 \(g(x)=\prod_{i=1}^n(x-x_i)\) ,则它相当于 \(h_i(x)=\frac{g(x)}{x-x_i}\)\(x_i\) 处的值。

可以发现 \(h_i(x)\)\(x_i\) 有定义,而 \(\frac{g(x_i)}{x-x_i}\) 却没有定义。这就意味着后者的极限即为所需:

\[\lim_{x\rightarrow x_i}\frac{g(x)}{x-x_i}=\lim_{x\rightarrow x_i}\frac{g(x)-g(x_i)}{x-x_i}=g'(x_i) \]

所以我们可以分治求出 \(g(x)\) 然后多点求值得到 \(\prod_{j\not=i}(x_i-x_j)\)

接下来求解 \(f(x)\) 继续采用分治。设 \(f_{l,r}(x)\)\([l,r]\) 的结果:

\[f_{l,r}(x)=\sum_{i=l}^r\left(\frac{y_i}{\prod_{j\not=i}(x_i-x_j)}\cdot\prod_{l\le j\le r,j\not=i}(x-x_j)\right) \]

设分治的划分点为 \(M\) ,那么可以得到:

\[f_{l,r}(x)=f_{l,M}(x)\cdot\prod_{i=M+1}^r(x-x_i)+f_{M+1,r}(x)\prod_{i=l}^M(x-x_i) \]

边界为 \(f_{i,i}(x)=\frac{y_i}{\prod_{j\not=i}(x_i-x_j)}\)

这个做法的复杂度是 \(O(n\log_2^2n)\)常数也就见仁见智吧


船新做法:还没有出现

常系数齐次线性递推

传统做法:

我们可以很容易地构造出转移矩阵 \(T\)

设初值的向量为 \(\bold{A}\) ,我们可以知道有:

\[a_n=(\bold{A}\times T^n)_0 \]

根据递推式,当整数 \(n\ge k\) 时有:

\[(\bold{A}\times T^n)_0=\sum_{i=1}^kf_i(\bold{A}\times T^{n-i})_0 \]

尝试将 \(()_0\) 去掉,也就是要说明对于整数 \(m\in[0,k)\) 都有:

\[(\bold A\times T^n)_m=\sum_{i=1}^kf_i(\bold A\times T^{n-i})_m \]

显然有:

\[(\bold A\times T^n)_m=(\bold A\times T^{n+m})_0=a_{n+m} \]

所以可以得到:

\[\bold A\times T^{n}=\sum_{i=1}^kf_i\times \bold A\times T^{n-i} \]

考虑 \(\bold A\) 不为 0 向量的时候,所以:

\[T^n=\sum_{i=1}^kf_iT^{n-i} \]

这是一个关于 \(T\) 的等式,也告诉我们 \(T^n\) 可以被表示为较低次项的多项式。所以我们可以对其最高次项频繁进行展开,直到多项式次数降为 \(k-1\) ;这个过程相当于是进行多项式取模:

\[T^n\bmod{\left(T^k-\sum_{i=0}^{k-1}f_{k-i}T^i\right)} \]

因此,如果我们得到取模的系数,我们就可以将初值带入得到 \(a_n\) 的值。

所以我们需要对多项式做快速幂,模式就为 \(T^k-\sum_{i=0}^{k-1}f_{k-i}T^i\)

时间复杂度是 \(O(k\log_2n\log_2k)\)

背后的严格证明就略过吧


船新做法:[3]

常系数齐次线性递推有其一般形式。设 \(A(x)\) 为数列 \(\{a_n\}\) 的生成函数,则:

\[A(x)=\frac{P(x)}{Q(x)} \]

其中 \(Q(x)\) 与递推式 \(\{f_k\}\) 的生成函数 \(F(x)\) 相关,即 \(Q(x)=1-F(x)\)\(P(x)\)\(A(x),Q(x)\) 均相关。

来源:

\(a_n\) 的递推式改一改得到生成函数形式:

\[\begin{aligned} a_nx^n&=\sum_{i=1}^kf_ix^i\cdot a_{n-i}x^{n-i}\\ \Rightarrow A(x)&=F(x)A(x) \end{aligned} \]

但是这里的结果并不完整,因为当 \(n\ge k\) 的时候递推式才是有效的,当 \(n<k\) 的时候,需要进行一些修补。

怎么修?很好办,减去不对的再加回对的就可以了,所以有:

\[P(x)\equiv (1-F(x))A(x)\pmod{x^{k}} \]

根据这个形式我们可以得到什么?

进行一些充满想象力的变换:

\[A(x)=\frac{P(x)Q(-x)}{Q(x)Q(-x)} \]

\(V(x)=Q(x)Q(-x)\) 。注意到 \(V(x)=V(-x)\)所以 \(V(x)\) 没有奇数次项

这意味着我们进行更深入的变化。设 \(U(x^2)=V(x)\) ,那么有:

\[A(x)=\frac{G_0(x^2)}{U(x^2)}+x\frac{G_1(x^2)}{U(x^2)} \]

其中 \(G_0(x^2)\)\(P(x)Q(-x)\) 的偶数次项对应的多项式, \(xG_1(x^2)\)\(P(x)Q(-x)\) 的奇数次项对应的多项式。

由于我们只需要求 \([x^n]A(x)\) ,所以我们可以舍弃一半的多项式,也就相当于是多项式长度不变,次数减半。最终我们只需要求某个分式的 \([x^0]\) ,就可以直接计算了。

这个算法的复杂度也是 \(O(k\log_2n\log_2k)\) ,但是显然它会好写一些,常数也会小一些。

据测试,除开各种复杂的优化,朴素的该算法的常数也仅有传统做法的 \(\frac 1 3\) 左右。


  1. 参考来自 EI 的博客↩︎

  2. 这里我们不必考虑 \(k<0\) 的项,因此 \(\deg H=\deg F\)↩︎

  3. 参考来自 EI 的博客↩︎

posted @ 2021-02-16 19:58  crashed  阅读(85)  评论(0编辑  收藏  举报