从多项式入门到形式幂级数
好好好。
开了个大坑。
我估计写一半就发布了。如果没了下文属于正常(
目前的前置知识:加减乘除,幂运算,未知数
多项式
单项式是这个:\(kx^n\)
多项式就是多个单项式相加,一般表示为:\(F(x) = a_0+a_1x+a_2x^2+...+a_nx^n\)
可以用求和符号简写成
其中称 \(a_0\) 为常数项,\(a_ix^i\) 为 \(i\) 次项,多项式的次数就是最高次项的次数 \(n\)。
(求和符号 \(\sum_{i=a}^{b}\) 代表从 \(i\) 从 \(a\) 到 \(b\) 遍历每一个数然后求和)
一般用大写字母表示多项式。当然我可能有时忘了(
之后会遇到无限次多项式:\(F(x) = a_0+a_1x+a_2x^2+...\)
不过不要惊慌,无非就是项数多了点。
多项式简单运算
多项式加法:\(H(x) = F(x)+G(x)\)
所以有 \(h_i= f_i + g_i\)
多项式减法:\(H(x) = F(x)-G(x)\)
有 \(h_i= f_i - g_i\)
一般减法为加法的逆运算,在有减法逆元(你可以理解为负数)的情况下,减法就是加负数。
多项式乘法:\(H(x) = F(x)\times G(x)\)(简写为 \(H(x) = F(x)G(x)\))
用乘法分配律拆一下式子
由于 \(f_ix^i \times g_jx^j = f_ig_jx^{i+j}\),所以 \(F\) 第 \(i\) 项系数 \(f_i\) 和 \(G\) 第 \(j\) 项系数 \(g_i\) 会加到 \(H\) 的第 \(i+j\) 项 \(h_{i+j}\) 上面。
从而 \(h+i = f_ig_0+f_{i-1}g_1+...+f_1g_{i-1} + f_0g_i\),即
有 \(h_i= \sum_{j=0}^{i}f_jg_{i-j}\)
请尽快理解求和符号(如果你不懂。
多项式的乘法,一般称作卷积。
多项式除法还不好定义。我们之后再讲。
所以多项式有什么用?
讲这么点,你可能不会问多项式有什么用。
但我还是讲一下吧(
首先不排除某些题就直接让你算多项式的情况(
其次多项式乘法的性质,及 \(f_ig_j \to h_{i+j}\),符合一些问题的计算,例如背包问题。
我们用 \(f_i\) 代表选择物品大小恰为 \(i\) 时的方案数,那么我们可以尝试计算这样一个多项式:第 \(i\) 项系数恰好为 \(f_i\)(即生成函数)。我们在计算后再取出系数,就可以得到答案。
我们考虑每物品选择是否选择,若选择,则会让物品大小增加 \(k\),此时 \(f_{i}\) 会贡献至 \(f_{i+k}\) 上,若不选择,则 \(f_{i}\) 贡献至他自己。
我们可以通过多项式表示这个过程,每次多一个物品,相当于乘上 \(1+x^k\) 这个多项式。之后我们要算的就是多个多项式相乘。
多项式乘法复杂度可以优化,所以可以通过将问题表示成多项式来简化问题,或者优化复杂度。
快速傅里叶变换 FFT
上面刚说完,现在讲讲多项式乘法怎么优化复杂度。但这很复杂,需要从虚数开始讲起,懂得可以自觉跳过。
虚数
前置知识:实数
定义虚数 \(i\),满足 \(i^2= -1\)。
所以有 \(i = \sqrt{-1}\)。众所周知,\(\sqrt{-1}\) 不在实数范围内,所以叫他虚数。
复数定义为实数+虚数,一般记为 \(z\):
其中 \(a,b\) 都为实数,\(a\) 为实部,\(bi\) 为虚部。
我们暂且记 \(a+bi\) 为 \((a,b)\)
C++里面是有复数的STL的,但建议还是手写(
复数运算
加法:\((a,b) + (c,d) = (a+c,b+d)\)
减法:\((a,b) - (c,d) = (a-c,b-d)\)
乘法:\((a,b) + (c,d) = (ac-bd,ad+bc)\)
乘法比较有趣,具体来说是这样:
除法可以通过乘上逆元来实现,逆元 \(z^-1\) 满足 \(z\times z^-1 = 1\)。
这里讲如何计算一个复数的逆元:
一般记成倒数第二个就行了。主要用到平方差公式。
复数除了 \(0\) 皆有逆元。
复平面
将复数 \(a+bi\) 表示成平面上的点 \((a, b)\)。实数就是 \(x\) 轴上的数。
复数加减法在复平面上类似于向量加减法。(向量或许之后用到会讲。
复数乘法比较神奇。
复数的模长记为 \(|z| = a^2 + b^2\)。即到原点的距离。
复数的幅角记为 \(\arg z\),即原点到其的射线与 \(x\) 轴夹角。
那么复数乘法满足:
\(|z_1z_2| = |z_1||z_2|\),模长相乘。
\(\arg z_1z_2 = \arg z_1 + \arg z_2\),幅角相加。
可以画图理解。这里就不证明了。
单位根
\(x^n=1\) 的 \(n\) 个解都是 \(n\) 次单位根。即 \(n\) 次单位根有 \(n\) 个。
单位根在复平面单位圆上,\(n\) 次单位根为单位上从 \((1,0)\),开始的 \(n\) 个等分点。
记从 \((1, 0)\) 逆时针旋转下一个单位根为 \(w_n\)。也相当于就是相邻两个单位根之间的间隔。
则有从 \((1, 0)\) 逆时针旋转第 \(k\) 个单位根为 \(w_n^k\)。(你理解的可能差个 \(O(1)\) 的常数,反正差不多就这意思(
单位根具有如下性质:
- \(w_n^n = 1\) 走一圈回来了。
 - \(w_n^k=w_{2n}^{2k}\) 相当于点的密度增加一倍,但是位置不变。
 - \(w_{2n}^{k+n}=-w_{2n}^{k}\) 走一半相当于实部和虚部同时取反。
 
多项式表示法
多项式可以表示成一开始的方式:\(F(x) = a_0+a_1x+a_2x^2+ \dots + a_nx^n\)
然后可以看做一个向量 \((a_0, a_1, \dots ,a_n)\)
这上面都是系数表示法,就是记录系数以记录一个多项式。
还有一种点值表示法。
通过记录 \(n\) 次多项式 \(n + 1\) 个位置的点值可以还原多项式。(一个基本方法是待定系数法)
也可以表示成一个点值向量 \((y_0, y_1, \dots ,y_n)\) 前提是规定所有 \(x_n\)。
一般情况下,多使用系数表示法。
快速傅里叶变换
好的终于开始正题了。
如果我们直接暴力求系数表示法的多项式乘法,则复杂度为 \(\operatorname{O}(n^2)\)。
需要枚举 \(f_i,g_j\) 并贡献到 \(h_{i+j}\)。
但是当我们求点值表示法下的多项式乘法,则只需要将每个点值相乘。复杂度 \(\operatorname{O}(n)\)
FFT使用的便是这个原理。
通过 FFT \(\operatorname{O}(n\log n)\) 求出 \(f, g\) 的点值表示,然后 \(\operatorname{O}(n)\) 得到 \(h\) 的点值表示,再用 IFFT \(\operatorname{O}(n\log n)\) 反向计算出 \(h\) 的系数表示。
快速傅里叶变换 FFT
我们有一个多项式 \(F(n) = \sum_{i=0}^{n-1} f_i x^i\)(注意次数是 \(n-1\)),我们需要求出其在任意 \(n\) 个位置 \(x_0, \dots, x_{n-1}\)的点值。
FFT 取 \(x_i = w_n^i\),根据单位根 \(w_{2n}^{2k} = w_{n}^{k}\) 的性质,我们可以将多项式分为奇偶项。
为了方便,我们先将多项式通过在系数表示后面加 \(0\) 将 \(n\) 扩充到 \(2\) 的整数幂。
我们把其分成奇偶项:
那么有
我们考虑求出 \(F0\) 和 \(F1\) 在每一个 \(\frac{n}{2}\) 单位根的值后如何得到 \(F\) 在每一个 \(n\) 次单位根下的值。
因为有 \(w_{2n}^{2k} = w_{n}^{k}\),所以可以得到:
那你发现这样就可以从 \(F0(w_{\frac{n}{2}}^i), F1(w_{\frac{n}{2}}^i)\) 直接得出 $$F(w_n^i)$$。
而 \(F0(w_{\frac{n}{2}}^i), F1(w_{\frac{n}{2}}^i)\) 是确定的。(当 \(\frac{n}{2} \leq i\) 时,\(w_{\frac{n}{2}}^i = w_{\frac{n}{2}}^{i - \frac{n}{2}}\))
实现方法多样,有很多优化空间,我这里先不讲了(
逆快速傅里叶变换 IFFT
我们有一个多项式 \(F(n) = \sum_{i=0}^{n-1} f_i x^i\),我们需要知道其在 \(x=w_n^i(0\leq i < n)\) 的 \(n\) 个位置的值 \(y_i(0 \leq i < n)\)。
我们要求出其每一项系数。
IFFT表示,我们将 \(y_i\) 作为 \(i\) 次项系数构造函数 \(G(x)\),那么 \(G(w_n^{-i})\) 就是 \(F(n)\) 的 \(i\) 次项系数。
我们推一下式子:
当 \(j-k = 0\) 时,
否则
所以 \(G(w_n^{-k}) = na_k\)。
至此,快速傅里叶变换就讲完了,正变换和逆变换本质上是同一个东西。
快速数论变换 NTT
快速傅里叶变换有个缺点。
其需要用到复数单位根 \(w_n = \cos(\frac{2\pi}{n}) + \sin(\frac{2\pi}{n})i\)。
而复数单位根不可避免的要涉及三角函数 \(\sin \cos\),会带来大量精度误差。(\(10^-6\) 左右好像)
而且过程中不可取模。
NTT便是取模意义下的 FFT,其使用数论中的原根替代单位根。因为原根具有和单位根相同的性质。
阶
整数 \(a\) 模 \(m\) 意义下的原根定义为最小的正整数 \(n\) 满足 \(a^n \equiv 1 \pmod p\),记为 \(\delta_m(a)\)
原根
使得 \(\delta_m(g)=\phi(m)\) 的整数 \(g\) 被称为模 \(m\) 意义下的原根,或 \(m\) 的原根。
原根可能不存在,也可能有多个。
(若一个数有原根,则原根个数为 \(\varphi(\varphi(m))\))
(\(m\) 有原根当且仅当 \(m=2,4,p^\alpha,2p^\alpha\),其中 \(p\) 为奇质数)
一个数 \(m\) 的最小原根为 \(\operatorname{O}(\log m)\)
NTT
一般选择模数为质数 \(p\),则原根 \(g\) 满足 \(g^{p-1} \equiv 1 \pmod p\)。
由于要将单位圆均分成 \(n\) 份,需要让 \(n\) 能够整除 \(p-1\)。同时我们的 \(n\) 是 \(2\) 的整数幂。
则 \(p = qn+1\),则需要一个足够大的 \(p\),满足 \(p = q2^k+1\)。
满足这个条件的的质数,题目中一般选择 \(998244353 = 7 \times 17 \times 2^23 + 1\)。\(3\) 为其原根。
接下的操作皆与 FFT 一致,只需要把 \(w_n\) 换成 \(g^\frac{p-1}{n}\) 并把所有操作带上模 \(p\) 即可。
一般题目若有取模则使用 NTT,若需求小数运算则只能用 FFT。
任意模数多项式乘法(MTT)
如果阴间出题人出了个模 \(998244853\) 的多项式题,我们如何对敌?
注意到模数没有原根,或者 \(p-1\) 无法被 \(2^k\) 整除。我们无法直接使用 NTT。
同时答案过大,我们无法直接使用 FFT。
多模数NTT
对于 NTT,我们只能用固定的几个模数去做。
所以我们考虑选择几个有原根的模数。然后使用扩展中国剩余定理,推出原系数时取模。(用中国剩余定理可能会炸 long long)
比如说 \(469762049, 998244353, 1004535809\),三个数的原根皆为 \(3\)。(可以不相同,但相同更方便)
拆系数FFT
也可以将多项式的系数 \(a_i\) 拆成 \(kb_i + c\),其中 \(k\) 是一个常数。
那么我们假设拆成这样:\(F(x) = kA(x) + B(x), G(x) = kC(x) + D(x)\)
于是有
算 \(4\) 次 FFT 和 \(4\) 次 IFFT 就可以了。
把 \(A(x)D(x) + B(x)C(x)\) 加在一起后同时 IFFT 可以节省一次 IFFT 次数。
多项式运算
现在我们来讲一下如何快速进行某些多项式的运算。
多项式求逆(多项式除法)
有一多项式 \(F(x) = \sum_{i=0}^{n-1} f_ix^i\),我们要求出其在模 \(x^n\) 意义下的逆 \(G(x)\)。
即,\(F(x)G(x) \equiv 1 \pmod{x^n}\)。
运用分治的思想。考虑先求出 \(G_0(x)\),使得 \(F(x)G_0(x) \equiv 1 \pmod{x^{\frac{n}{2}}}\)。
容易发现其实 \(G_0(x)\) 就是 \(G(x)\) 的前 \(\frac{n}{2}\) 项,即:
两边以及模数同时平方。因为左侧在模数以下的项系数皆为 \(0\)。
我们就可以将模数变成 \(x^n\)。这时候两边同乘 \(F(x)\),得到:
可以使用多项式乘法递归计算。复杂度:\(T(n) = T(\frac{n}{2}) +O(n\log n) = O(n\log n)\)
多项式求导
所以直接对每一项求导,可以 \(O(n)\) 算。
多项式积分
积分是求导的逆运算。
所以同样可以 \(O(n)\) 算。
多项式 ln
所以我们将求导求逆乘法积分直接算就行了。
                    
                
                
            
        
浙公网安备 33010602011771号