浅谈基于转置原理的多项式多点求值算法
考虑一个经典的问题。给定 $n$ 次多项式
和 $n+1$ 个点值 $a_0,a_1,\dots,a_n$。您需要对于每个 $k$,求出 $b_k=F(a_k)$,对 $998244353$ 取模。
据说以前这种问题都是采用多项式取模的方法。但是现在我们有新科技了。
本篇文章将会以我个人认为十分通俗易懂的方式来详细地讲解这种新科技。
参考文献略。
线性算法
若一个算法可以被看作是方阵 $A$,输入向量 $\mathbf v$,输出向量 $A\mathbf v$,则称该算法为线性算法。
比如说,FFT 就是一个线性算法,其对应的方阵为
转置原理
对于一个线性算法,设其对应的方阵 $A$ 满足
其中 $E_i$ 均为初等矩阵。则有
这里稍微复习一下,初等矩阵指的是只进行一次初等变换的矩阵。这里的初等变换包括
- $v_i\leftarrow v_i\cdot k$
- $v_i\leftarrow v_i+v_j\cdot k$
如果 $E_i$ 对应第一种初等变换,它的转置就是自身;如果是第二种,转置后 $i$ 和 $j$ 互换,即对应的初等变换变为 $v_j\leftarrow v_j+v_i\cdot k$。
这里的关键点在于,如果 $A\mathbf v$ 比较好求,而 $A^{\mathrm T}\mathbf v$ 相对难求,我们可以将求 $A\mathbf v$ 的过程分析出来,把每一步转置然后倒序执行,就可以求出 $A^{\mathrm T}\mathbf v$ 了。
多项式乘积的转置
考虑两个多项式
和
令它们的乘积为
考虑计算这一乘积的算法。为了便于理解,我们将 $G(x)$ 视作常量,$F(x)$ 和 $H(x)$ 视作变量,考虑一个作用在如下 $(n+1)+(n+m+1)=2n+m+2$ 维列向量上的线性算法
初始时 $f_i$ 已经有值,而 $h_i$ 为 $0$。算法流程如下:
- 循环变量 $i\leftarrow 0\dots n+m$
- 循环变量 $j\leftarrow \max(0,i-m)\dots\min(n,i)$
- 执行 $h_i\leftarrow h_i+f_j\times g_{i-j}$
考虑这一过程的转置,其流程如下:
- 循环变量 $i\leftarrow n+m\dots 0$
- 循环变量 $j\leftarrow \min(n,i)\dots\max(0,i-m)$
- 执行 $f_j\leftarrow f_j+h_i\times g_{i-j}$
相当于在已知 $H(x)$ 和 $G(x)$ 的情况下求解 $F(x)$。其中
其中 $g^{\mathrm R}_i=g_{m-i}$,即 $G$ 的所有系数翻转过来。
于是我们可以如下定义多项式乘积的转置
不难发现,如果 $F(x)$ 是 $n$ 次多项式,$G(x)$ 是 $m$ 次多项式,那么 $(F\times^{\mathrm T}G)(x)$ 会是 $n-m$ 次多项式。
正文
令
我们有
考虑
不难发现
定义生成函数
则
这里 $C(x)$ 可以通过经典的分治 NTT 求解。令
对于叶子节点 $[i,i]$,执行
对于内部节点 $[l,r]$,执行
注意到,$Q_{l,r}(x)$ 实际上和 $f_i$ 没有任何关系,我们可以把它们视作常量。然后把剩下的 $(n+1)^2$ 个 $P_{l,r}(x)$ 全部塞进一个向量里,那么上述分治过程就可以视作是一个针对该向量的线性算法。
我们可以写得再清晰一些,比如说像下面这样
以及最后一步的
那么转置之后,第一步自然就是
想不明白为什么吗?我也想不明白。
在这之后,从上向下分治,对于内部节点 $[l,r]$,执行
最终对于叶子节点 $[i,i]$,$B_{i,i}(x)$ 必定会是一个 $0$ 次多项式,其仅有的一个常数项系数就是我们要求的 $b_i$ 了。
当然,如果多项式次数和点值数量不对等也很容易处理,用 $0$ 补齐即可。
结语
这个算法好在哪里:
- 常数小:
std::vector
也不必太过担心被卡常。 - 代码实现难度小:只要能够完全理解整个算法流程,便能够十分迅速且正确地写出代码。