多项式全家桶

打算开 GF 这个万恶之源了,但在此之前先把多项式的那堆板子理理清楚吧。代码没有刻意卡常,而且写成的年代不同,码风和实现方法会有一点不一样,板子也不会太全,之后会遇到问题会在这里慢慢补充。

多项式乘法

给定一个 \(n\) 次多项式 \(F(x)=\sum_{i=0}^n f_ix^i\)\(m\) 次多项式 \(G(x)=\sum_{j=0}^m g_jx^j\),求出 \(F(x)\)\(G(x)\) 的卷积:

\[\sum_{i=0}^n\sum_{j=0}^m f_ig_jx^{i+j} \]

注意到多项式乘法的过程,单从卷积定义角度考虑是难以优化的,所以我们从多项式本身考虑。

多项式有两种表示方法,点值表示法和系数表示法。其中系数表示法就是我们常用的形式:

\[F(x)=\{f_0,f_1,f_2,\cdot\cdot\cdot,f_n\} \]

其中 \(f_i\) 表示 \(x^i\) 前面的系数。这显然可以唯一确定一个 \(n\) 次多项式。而点值表示法是这样的:

\[F(x)=\{(x_0,F(x_0)),(x_1,F(x_1)),\cdot\cdot\cdot,(x_n,F(x_n))\} \]

注意到这 \(n+1\) 个点也能表示一个 \(n\) 次多项式,可以从高斯消元的角度考虑。

显然我们常用的(题目输入和要求输出的)都是第一种表示方法,那第二种表示方法有啥用呢。对于两个 \(n\) 次多项式 \(F(x),G(x)\),它们的卷积 \(H(x)\) 在点值表示法下能很轻易地被求出:

\[H(x)=\{(x_0,F(x_0)G(x_0)),(x_1,F(x_1)G(x_1)),\cdot\cdot\cdot,(x_n,F(x_n)G(x_n))\} \]

时间复杂度仅有 \(\mathcal{O}(n)\)。但问题是题目不认点值表示法,而暴力在两种表示方法之间的转化复杂度是很高的。所以我们现在的任务就是找到在两种表示方法之间转化的合适方法。

FFT/快速傅里叶变换

前置知识:复数。

可以做到在 \(\mathcal{O}(n\log n)\) 的时间复杂下完成点值表示法和系数表示法的相互转化。

我们先来看从系数表示法到点值表示法,注意到这里的瓶颈在于算 \(x^i\) 和计算 \(F(x^i)\)。这两个问题本质上其实挺像的,都是要求寻找一个合适的 \(x\),使得 \(x^i\) 具有一些美妙的性质。

注意到 \(1,-1\) 的幂都是很好算的,但这样仅仅够我们算两个点,一共可是需要 \(n+1\) 个。所以我们要更多的点,更具体地讲,我们需要更多满足 \(|\omega^k|=1\)\(\omega\)。可以发现需要引入复数了,\(i,-i\) 显然是方程的解,除此之外,在单位圆上所有向量对应的复数都满足:

(应该都能看出来是从 OI wiki 拿的图吧)

严谨讲,我们定义 \(x^n=1\)\(\mathbb{C}\) 中的解是 \(n\) 次复根。根据上图,我们能发现这样的解有 \(n\) 个,根据欧拉公式 \(e^{ix}=\cos x+i\sin x\),我们定义:

\[\omega_n=e^{\frac{2\pi i}{n}} \]

为单位复根,可以发现这个复数对应了把单位圆 \(n\) 等分的第一个角对应的向量。则 \(x^n=1\) 的解集能用 \(w_n\) 的幂表示:

\[\{\omega^k_n|k\in[0,n)\cap\mathbb{Z}\} \]

说了这么多,真正要投入应用的话,我们还需要知道单位复根的一些性质。对于任意的正整数 \(n\) 和整数 \(k\),有:

\[\omega^n_n=1,w_n^k=\omega^{2k}_{2n},w_{2n}^{k+n}=-w_{2n}^k \]

均可以通过把复数看成向量,用几何意义证明,不再赘述。而有了这些性质,就可以开始进入正片了。

FFT 的基本思路是分治,递归处理当 \(x=w_n^k\) 时,\(f(x)\) 的值。我们来举个例子吧,\(8\) 项的多项式:

\[f(x)=a_0+a_1x+a_2x^2+\cdot\cdot\cdot+a_7x^7 \]

考虑奇偶分治,把式子按照下标的奇偶性分成两坨:

\[\begin{aligned}f(x)&=(a_0+a_2x^2+a_4x^4+a_6x^6)+(a_1x^1+a_3x^3+a_5x^5+a_7x^7)\\&=(a_0+a_2x^2+a_4x^4+a_6x^6)+x(a_1+a_3x^2+a_5x^4+a_7x^6)\end{aligned} \]

分别对奇偶项建立新函数:

\[g(x)=a_0+a_2x+a_4x^2+a_6x^3,h(x)=a_1+a_3x+a_5x^2+a_7x^3 \]

则显然有:

\[f(x)=g(x^2)+xh(x^2) \]

好了,现在就可以代入 \(\omega^k_n\) 了:

\[\begin{aligned}f(\omega^k_n)&=g((\omega^k_n)^2)+\omega^k_nh((\omega^k_n)^2)\\&=g(\omega^{2k}_{n})+\omega^k_nh(\omega^{2k}_{n})\\&=g(\omega_{\frac{n}{2}}^k)+\omega^k_nh(\omega^k_{\frac{n}{2}})\end{aligned} \]

好像还是看不出来什么,再代入个 \(\omega^{k+\frac{n}{2}}_n\) 试试?推导过程省略,跟上面差不多,我们能得到:

\[f(\omega^{k+\frac{n}{2}}_n)=g(w_{\frac{n}{2}}^k)-\omega^k_nh(\omega^k_{\frac{n}{2}}) \]

好了,现在我们就完全能看出来了,求出 \(g(\omega_{\frac{n}{2}}^k),h(\omega^k_{\frac{n}{2}})\) 之后我们就能一次处理处两个函数值!而处理这俩函数的过程又是一次递归的过程!这下找到了方向了,我们对于每个函数值,需要代入 \(n\) 个不同的值,每次会把多项式长度缩减 \(\frac{1}{2}\),所以最终复杂度就是 \(\mathcal{O}(n\log n)\)

值得注意的是,分治的时候需要保证每次分治两边长短相同,换句话说,需要 \(n=2^m,m\in\mathbb{N}\)。如果原多项式不满足的话,就用 \(0\) 补就好了。

好了,现在我们能把系数表示法换成点值表示法了,愉快地把函数值相乘,然后呢?然后我们就需要把点值表示法换成系数表示法了。这一过程通常被称为 IFFT,即逆 FFT。

考虑原本的多项式是 \(f(x)=\sum_{i=0}^{n-1} a_ix^i\),而现在我们已知 \(y_i=f(\omega_n^i),i\in[0,n)\cap\mathbb{Z}\),现在要求 \(\{a_0,a_1,\cdot\cdot\cdot,a_{n-1}\}\)。考虑设:

\[A(x)=\sum_{i=0}^{n-1}y_ix^i \]

现在我们就要在 \(A(x)\) 上面搞搞事了。

考虑将 \(\omega_n^{-i}\) 分别代入 \(A(x)\),则有:

\[\begin{aligned}A(\omega_n^{-k})&=\sum_{i=0}^{n-1}f(\omega_n^{i})\omega_n^{-ik}\\&=\sum_{i=0}^{n-1}\omega_n^{-ik}\sum_{j=0}^{n-1}a_j\omega^{ij}\\&=\sum_{i=0}^{n-1}\sum_{j=0}^{n-1}a_j\omega^{i(j-k)}\\&=\sum_{j=0}^{n-1}a_j\sum_{i=0}^{n-1}\omega^{i(j-k)}\end{aligned} \]

\(S(\omega_n^a)=\sum_{i=0}^{n-1}(\omega_n^{ai})\),则当 \(a\equiv0\pmod{n}\) 时,显然 \(S(\omega_n^a)=n\)(因为 \(\omega_n^a=1\) 嘛)。

而当 \(a\not \equiv0\pmod{n}\) 时,考虑经典 trick 错位相减:

\[\begin{aligned}S(\omega_n^a)&=\sum_{i=0}^{n-1}(\omega_n^{ai})\\\omega_n^{a}S(\omega_n^a)&=\sum_{i=1}^{n}(\omega_n^{ai})\\\therefore S(\omega_n^a)&=\dfrac{\omega_n^{an}-\omega_n^{a0}}{\omega_n^a-1}=0\end{aligned} \]

综上,我们有:

\[S(\omega_n^a)=\begin{cases}n&a\equiv0\pmod{n}\\0&a\not \equiv0\pmod{n}\end{cases} \]

带回原式:

\[A(\omega_n^{-k})=\sum_{j=0}^{n-1}a_jS(\omega_n^{j-k})=a_kn \]

非常神奇,如果令 \(b_k=\omega_n^{-k}\),则 \(A\) 的点值表示法就是:

\[\{(b_0,a_0n),(b_1,a_1n),\cdot\cdot\cdot,(b_{n-1},a_{n-1}n)\} \]

综上,我们取单位根为其倒数,然后对 \(A\) 做一遍上述的 FFT 过程,再除以 \(n\) 就得到了系数表示法。

递归版实现:\(\tt code\)

好的现在您写完了递归版,非常愉快地交了模板题,非常愉快地获得了 \(\tt 100pts\),但这一切快乐都在您打开提交记录,按照运行时间排序后结束了,“他们的为啥跑的这么快??”

直觉告诉我们是递归的锅。递归带来了大常数,让本就因为实数运算常数不小的 FFT 雪上加霜。所以我们要考虑把它变成非递归版本。

考虑模拟算法中递归分治的过程:

\[\{0,1,2,3,4,5,6,7\}\\\{0,2,4,6\}\{1,3,5,7\}\\\{0,4\}\{2,6\}\{1,5\}\{3,7\}\\\{0\}\{4\}\{2\}\{6\}\{1\}\{5\}\{3\}\{7\} \]

分完了。看不出来什么?考虑把分治前的数和分治后的数都变成二进制表示:

\[\{000,001,010,011,100,101,110,111\}\\\{000,100,010,110,001,101,011,111\} \]

这下明显了吧,分治前后的数二进制位是反过来的!那我们只需要提前把数交换到对应的位置,然后模拟递归的过程就好了。

现在的问题是怎么把二进制位给反过来。\(\mathcal{O}(n\log n)\) 固然是一种方法,不过我们能做到 \(\mathcal{O}(n)\)。考虑递推,设 \(rev_x\) 表示 \(x\) 的二进制位反过来的结果,显然有 \(rev_0=0\)。而对于一个数 \(x\),如果除去最高位不算,\(rev_x=\left\lfloor\dfrac{rev_{\lfloor\frac{x}{2}\rfloor}}{2}\right\rfloor\),也就是把 \(x\) 右移一位的数反转一下再右移一位。而对于最高位,它取决于 \(x\) 的奇偶性,所以有:

\[rev_x=\left\lfloor\dfrac{rev_{\lfloor\frac{x}{2}\rfloor}}{2}\right\rfloor+(x\bmod{2})2^{k-1} \]

其中 \(k\) 是二进制表示下的位数。

非递归版实现:\(\tt code\)

NTT/快速数论变换

前置知识:原根

刚刚我们看到了 FFT,这里还有一种思路类似的 NTT,是 FFT 在数论基础上的实现。由于 FFT 涉及到大量的实数运算,丢精度,速度慢就成为了不可避免的问题。而 NTT 是实现在数论基础上的,运算都是整数,准确度和速度都会更快,但时间复杂度依然是 \(\mathcal{O}(n\log n)\)

这俩的基本思路都差不多,而我们的关键就是在数论领域中找出一个东西来代替单位复根。我们发现,对于质数 \(p=qn+1(n=2^m)\),它的原根 \(g\) 恰好就满足刚刚所说的性质:

\[g^{qn}\equiv1\pmod{p} \]

而如果我们把 \(g^q\) 记作 \(g_{n}\),会发现它也有类似的性质,如:

\[g_{n}^n\equiv1\pmod{p},g_{n}^{\frac{n}{2}}\equiv-1\pmod{p} \]

然后就结束了,只需要把 FFT 里的单位复根扣掉换成原根就行了。常见的质数原根:

\[p=998,244,353=7\times17\times2^{23}+1,g=3 \]

其余的质数可以参考求原根的方法考场现求。

实现:\(\tt code\)

分治 FFT/NTT

给出序列 \(g_{1\sim n}\),求出序列 \(f_{0\sim n}\),其中:

\[f_0=1,f_i=\sum_{j=1}^i f_{i-j}g_j \]

答案对 \(998,244,353\) 取模。

能看到很明显的卷积特征,但很遗憾,我们不能直接进行一个积的卷,因为对于 \(f_i\) 来说,它的值是依赖以前求出来的值的,换句话说,我们要求在线的卷积。(这也是为什么这个算法在国外有时也被称为在线卷积)

考虑参考 \(\rm cdq\) 分治的思路,递归处理区间。先处理左区间,然后处理左区间对有区间的贡献,最后处理右区间,这样能保证每次卷积需要的值都已经被求出了。

具体来讲,对于当前区间 \([l,r)\),我们先递归求出 \([l,mid)\)\(f\) 的值,然后将 \(f_{l\sim mid-1}\)\(g_{0\sim r-l-1}\) 给卷起来,这样能得到左边对右边 \(f_{mid,r-1}\) 的贡献。之后递归处理右区间 \([mid,r)\) 即可。时间复杂度:

\[T(n)=2T\left(\dfrac{n}{2}\right)+\mathcal{O}(n\log n)=\mathcal{O}(n\log^2 n) \]

实现:\(\tt code\)

注意卷积之前要补 \(0\) 清空而不是不管!

MTT

任意模数 NTT,不会。

多项式牛顿迭代

给出多项式 \(g(x)\),已知存在一个多项式 \(f(x)\),满足:

\[g(f(x))\equiv0\pmod{x^n} \]

求出模 \(x^n\) 意义下的 \(f(x)\)

考虑倍增的思路。首先考虑边界,对于 \(n=1\) 的情况,我们需要单独求出 \([x^0]g(f(x))=0\) 的解。然后对于其余的 \(n\),我们考虑先递归计算 \(\left\lceil\dfrac{n}{2}\right\rceil\) 的情况,假设得到在模 \(x^{\lceil\frac{n}{2}\rceil}\) 意义下的解是 \(f_0(x)\),则现在我们的目标就是要用 \(f_0(x)\) 表示 \(f(x)\)

考虑将 \(g(f(x))\)\(f_0(x)\) 处泰勒展开,则原同余方程可化为:

\[\sum_{i\ge 0}\dfrac{g^{(i)}(f_0(x))}{i!}(f(x)-f_0(x))^i\equiv0\pmod{x^n} \]

其中 \(g^{(i)}\) 表示 \(g\)\(i\) 阶导。注意到 \(f(x)-f_0(x)\) 这个式子的最低非 \(0\) 项次数最低是 \(\lceil\frac{n}{2}\rceil\),因为在这之前它们都一样,所以有:

\[\forall i\ge 2:(f(x)-f_0(x))^i\equiv0\pmod{x^n} \]

这样,原同余方程又能化为:

\[g(f_0(x))+g'(f_0(x))(f(x)-f_0(x))\equiv0\pmod{x^n} \]

简单的代数变化:

\[f(x)\equiv f_0(x)-\dfrac{g(f_0(x))}{g'(f_0(x))}\pmod{x^n} \]

这下式子有了,至于怎么求,怎么用,且听下文分解。

多项式求逆

给定一个多项式 \(f(x)\),求出一个多项式 \(g(x)\) 满足:

\[f(x)g(x)\equiv1\pmod{x^n} \]

系数对 \(998,244,353\) 取模。

考虑套刚刚的多项式牛顿迭代。为了避免跟上文的符号矛盾,我们记待求逆的函数为 \(h(x)\),求逆结果函数为 \(f(x)\),则有:

\[g(f(x))=\dfrac{1}{f(x)}-h(x)\equiv0\pmod{x^n} \]

套牛顿迭代的式子有:

\[\begin{aligned}f(x)&\equiv f_0(x)-\dfrac{\frac{1}{f_0(x)}-h(x)}{-\frac{1}{f^2_0(x)}}\pmod{x^n}\\&\equiv f_0(x)(2-f_0(x)h(x))\pmod{x^n}\end{aligned} \]

注意这里求导的时候把 \(h(x)\) 当成常数来做了,然后就结束了,时间复杂度:

\[T(n)=T\left(\dfrac{n}{2}\right)+\mathcal{O}(n\log n)=\mathcal{O}(n\log n) \]

实现:\(\tt code\)

注意,在实现牛顿迭代的时候,类似分治 FFT,卷积之前一定要记得清空不用的!

多项式开根

给出一个 \(n-1\) 次多项式 \(A(x)\),找出一个模 \(x^n\) 意义下的多项式 \(B(x)\) 使得

\[B^2(x)\equiv A(x)\pmod{x^n} \]

系数对 \(998,244,353\) 取模。

依然考虑牛顿迭代。类似求逆的推导过程,我们依然记 \(h(x)\) 为待开根函数,\(f(x)\) 为答案,则有:

\[g(f(x))=f^2(x)-h(x)\equiv0\pmod{x^n} \]

还是套牛顿迭代的式子:

\[\begin{aligned}f(x)&\equiv f_0(x)-\dfrac{f_0^2(x)-h(x)}{2f_0(x)}\pmod{x^n}\\&\equiv\dfrac{f_0^2(x)+h(x)}{2f_0(x)}\end{aligned} \]

多项式求逆就可以做不带余数的除法了。类似求逆,时间复杂度 \(\mathcal{O}(n\log n)\)

实现:\(\tt code\)

多项式求导/积分

这个比较简单了,主要是一些公式。

首先由于求导和积分的线性性:

\[\begin{aligned}(f(x)+g(x))'&=f'(x)+g'(x)\\(cf(x))'&=cf'(x)\\\int(f(x)+g(x))&=\int f(x)+\int g(x)\\\int cf(x)&=c\int f(x)\end{aligned} \]

所以对于多项式 \(F(x)=\sum_{i=0}^{n-1}a_ix^i\),它的导数和积分分别为:

\[\begin{aligned}F'(x)=\sum_{i=1}^{n-1}a_iix^{i-1}\\\int F(x)=\sum_{i=0}^{n-2}\dfrac{a_ix^{i+1}}{i+1}\end{aligned} \]

可以 \(\mathcal{O}(n)\) 求解。

还有一些比较常用的求导公式:

\[\begin{aligned}(F(x)G(x))'&=F'(x)G(x)+F(x)G'(x)\\\left(\dfrac{F(x)}{G(x)}\right)'&=\dfrac{F'(x)G(x)-F(x)G'(x)}{G^2(x)}\\(G(F(x)))'&=G'(F(x))F'(x)\end{aligned} \]

更多的东西比如积分公式啊,更多的求导公式啊,常见的函数的积分导数啊,建议大概学一下微积分。

多项式带余除法

给出一个 \(n\) 次多项式 \(F(x)\) 和一个 \(m\) 次多项式 \(G(x)\),求出多项式 \(Q(x),R(x)\) 满足以下条件:

  • \(Q(x)\) 次数为 \(n-m\)\(R(x)\) 次数小于 \(m\)
  • \(F(x)=Q(x)G(x)+R(x)\)

系数对 \(998,244,353\) 取模。

刚刚其实我们也做过分数运算(在牛顿迭代那一块),但这里不一样的是,题目还要求求出 \(R(x)\),即余数。这不太好办,所以考虑消去 \(R(x)\) 的影响。

有个很妙的思想 (不知道咋想到的) ,考虑构造多项式 \(F^R(x)\)

\[F^R(x)=x^nF\left(\dfrac{1}{x}\right) \]

容易想到 \(F^R(x)\) 的实质其实就把系数反转一下。顺着这个思路,将带余除法的等式两边同时乘上 \(x^n\) 并把函数里的自变量都改为 \(\frac{1}{x}\) 有“

\[\begin{aligned}x^nF\left(\frac{1}{x}\right)&=x^{n-m}Q\left(\frac{1}{x}\right)x^{m}G\left(\frac{1}{x}\right)+x^{n-m+1}x^{m-1}R\left(\frac{1}{x}\right)\\F^R(x)&=Q^R(x)G^R(x)+x^{n-m+1}R^R(x)\end{aligned} \]

诶,这个式子就特殊了,注意到只有 \(R^R(x)\) 这一项前面有一个 \(x^{n-m+1}\),那我们只需要把上式放在模 \(x^{n-m+1}\) 意义下不就把 \(R^R(x)\) 干掉了吗,即:

\[F^R(x)\equiv Q^R(x)G^R(x)\pmod{x^{n-m+1}} \]

问题是,这会不会对 \(Q^R(x)\) 造成影响导致我们求出的值不精确呢?显然不会,因为 \(Q^R(x)\) 的次数仅有 \(n-m\),小于模数的 \(n-m+1\) 次,所以 \(Q^R(x)\) 并不会受到影响。

这样求个逆就能把 \(Q(x)\) 求出来了。求出来之后再反代回去就有 \(R(x)\) 了。时间复杂度 \(\mathcal{O}(n\log n)\)

实现:\(\tt code\)

多项式 ln

给出 \(n-1\) 次多项式 \(A(x)\),求一个模 \(x^n\) 意义下的多项式 \(B(x)\) 满足:

\[B(x)\equiv \ln A(x)\pmod{x^n} \]

系数对 \(998,244,353\) 取模。

考虑设 \(G(x)=\ln x\),则原题相当于求 \(G(F(x))\),考虑给这玩意求个导:

\[(G(F(x)))'=G'(F(x))F'(x) \]

由于,

\[(\ln x)'=\dfrac{1}{x} \]

则上式为:

\[(G(F(x)))'=\dfrac{F'(x)}{F(x)} \]

这样我们就能求出带求多项式的导数了,之后只需要积分回来即可:

\[\int \dfrac{F'(x)}{F(x)} \]

这样直接求导+求逆+积分这题就做完了。时间复杂度 \(\mathcal{O}(n\log n)\)

实现:\(\tt code\)

多项式 Exp

给出 \(n-1\) 次多项式 \(A(x)\),求一个模 \(x^n\) 意义下的多项式 \(B(x)\) 满足:

\[B(x)\equiv e^{A(x)}\pmod{x^n} \]

系数对 \(998,244,353\) 取模。

考虑多项式牛顿迭代法。记 \(A(x)\)\(h(x)\)\(B(x)\)\(f(x)\),则有:

\[g(f(x))=\ln f(x)-h(x)\equiv0\pmod{x^n} \]

套式子:

\[\begin{aligned}f(x)&=f_0(x)-\dfrac{\ln f_0(x)-h(x)}{\frac{1}{f_0(x)}}\\&=f_0(x)(1-\ln f_0(x)+h(x))\end{aligned} \]

求个 \(\ln\),再加加减减后跟其他的乘起来就完事了。时间复杂度 \(\mathcal{O}(n\log n)\)

实现:\(\tt code\)

posted @ 2022-03-16 21:27  zhiyangfan  阅读(171)  评论(0编辑  收藏  举报