数学杂谈 #5

多项式多点求值

传统做法,原理是:

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

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

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

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

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

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


船新做法:[1]

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

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

这个做法的原理是:

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

自行展开即可得知。

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

[F imes (Gcdot H)=F imes G imes H ]

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

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

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

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

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

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

多项式快速插值

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

[f(x)=sum_{i=1}^nfrac{y_iprod_{j ot=i}(x-x_j)}{prod_{j ot=i}(x_i-x_j)} ]

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

将式子变形:

[f(x)=sum_{i=1}^nleft(frac{y_i}{prod_{j ot=i}(x_i-x_j)}cdotprod_{j ot=i}(x-x_j) ight) ]

首先处理 (prod_{j ot=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 ightarrow x_i}frac{g(x)}{x-x_i}=lim_{x ightarrow x_i}frac{g(x)-g(x_i)}{x-x_i}=g'(x_i) ]

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

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

[f_{l,r}(x)=sum_{i=l}^rleft(frac{y_i}{prod_{j ot=i}(x_i-x_j)}cdotprod_{lle jle r,j ot=i}(x-x_j) ight) ]

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

[f_{l,r}(x)=f_{l,M}(x)cdotprod_{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 ot=i}(x_i-x_j)})

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


船新做法:还没有出现

常系数齐次线性递推

传统做法:

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

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

[a_n=(old{A} imes T^n)_0 ]

根据递推式,当整数 (nge k) 时有:

[(old{A} imes T^n)_0=sum_{i=1}^kf_i(old{A} imes T^{n-i})_0 ]

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

[(old A imes T^n)_m=sum_{i=1}^kf_i(old A imes T^{n-i})_m ]

显然有:

[(old A imes T^n)_m=(old A imes T^{n+m})_0=a_{n+m} ]

所以可以得到:

[old A imes T^{n}=sum_{i=1}^kf_i imes old A imes T^{n-i} ]

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

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

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

[T^nmod{left(T^k-sum_{i=0}^{k-1}f_{k-i}T^i ight)} ]

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

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

时间复杂度是 (O(klog_2nlog_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) 的递推式改一改得到生成函数形式:

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

但是这里的结果并不完整,因为当 (nge 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)}+xfrac{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(klog_2nlog_2k)) ,但是显然它会好写一些,常数也会小一些。

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


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

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

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

原文地址:https://www.cnblogs.com/crashed/p/14404356.html