欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

多项式学习笔记

程序员文章站 2022-06-03 13:17:27
...

多项式基本表现形式

A(x)=aixi \mathbf A(x) = \sum a_i x^i

多项式乘法

A(x)=aixiB(x)=bixiC(x)=A(x)×B(x)=i=0n1aixij=1n1bjxj=i=02n2cixi \begin{aligned} \mathbf A(x) &= \sum a_i x^i\\ \mathbf B(x) &= \sum b_i x^i\\ \mathbf C(x) &= \mathbf A(x) \times \mathbf B(x)\\ &= \sum_{i = 0}^{n - 1} a_i \cdot x^i \sum_{j = 1}^{n - 1} b_j \cdot x^j\\ &= \sum_{i = 0}^{2n - 2} c_i \cdot x^i \end{aligned}

其中

ci=j=0iajbij \begin{aligned} c_i = \sum_{j = 0}^{i} a_j \cdot b_{i - j} \end{aligned}

多项式的表达方法

  • 系数表达

对于A(x)=aixi\mathbf A(x) = \sum a_i \cdot x^i,其系数表达就是由多项式系数组成的向量a=(a0,a1,,an1)a = (a_0, a_1, \dots, a_{n - 1})

用系数计算多项式乘法复杂度为O(n2)O(n^2)

  • 点值表达

对于A(x)=aixi\mathbf A(x) = \sum a_i x^i,其点值表达是由nn个点值组成的集合

{(x0,y0),(x1,y1),(xn1,yn1)} \{(x_0, y_0), (x_1, y_1), \dots (x_{n - 1}, y_{n - 1})\}

其中

ij,xixj \forall i\not = j, x_i\not = x_j

对于计算A(x)×B(x)\mathbf A(x) \times \mathbf B(x),我们将表示A(x)\mathbf A(x)B(x)\mathbf B(x)的点值口占,使每个多项式包含2n2n个点值对

{(x0,y0),(x1,y1),,(x2n1,y2n1)}{(x0,y0),(x1,y1),,(x2n1,y2n1)}{(x0,y0y0),(x1,y1y1),,(x2n1,y2n1y2n1)} \{(x_0, y_0), (x_1, y_1), \dots, (x_{2n - 1}, y_{2n - 1})\}\\ \{(x_0, y'_0), (x_1, y'_1), \dots, (x_{2n - 1}, y'_{2n - 1})\}\\ \{(x_0, y_0y'_0), (x_1, y_1y'_1), \dots, (x_{2n - 1}, y_{2n - 1}y'_{2n - 1})\}

拉格朗日插值

A(x)=k=0n1jkxxjjkxkxj \mathbf A(x) = \sum_{k = 0}^{n - 1} \frac{\prod_{j\not = k}x - x_j}{\prod_{j\not = k}x_k - x_j}

本原单位根

满足xn1=0x^n - 1 = 0xx被称作nn次单位根

如果我们有一个ω\omega,对于ω0,ω1,,ωn1\omega^0, \omega^1, \dots, \omega^{n - 1}互不相同,就称ω\omega为本原单位根,我们用ωn\omega_n表示为一个nn次本原单位根,且它们均匀地分布在以复平面的原点为圆心的单位半径的圆周上

其中

ωn0=ωnn=1,ωnn/2=1 \omega_n^0 = \omega_n^n = 1, \omega_n^{n / 2} = -1

利用复数的指数形式定义

eiu=cos(u)+isin(u) e^{iu} = cos(u) + i sin(u)

引理

ωnjωnk=ωnj+k=ωn(j+k)modnn0,k0,d>0,ωdndk=ωnkn>0,nmod2=0,ωnn/2=w2=1 \omega_n^j \omega_n^k = \omega_n^{j + k} = \omega_n^{(j + k)\bmod n}\\ \forall n \ge 0, k \ge 0, d > 0, \omega_{dn}^{dk} = \omega_n^k\\ \forall n > 0, n \bmod 2 = 0, \omega_n^{n / 2} = w_2 = -1

DFT

将多项式的系数表示转变为点值表示,设a\mathbf a是长度为nn的序列,对于0k<n0\leq k<n,令

bk=i=0n1aiωnki b_k = \sum_{i = 0}^{n - 1} a_i \cdot \omega_n^{ki}

则称b\mathbf ba\mathbf a的离散傅里叶变换DFT,记作b=DFT(a)\mathbf b=\mathbf {DFT}(\mathbf a)

IDFT

将多项式的点值表示转变为系数表示,DFT的逆变换

b\mathbf b得到a\mathbf a,我们有公式

ak=1ni=0n1biωnki a_k = \frac{1}{n} \sum_{i = 0}^{n - 1} b_i \cdot \omega_n^{-ki}

证明,将上面两个式子结合

1ni=0n1biωnki=1ni=0n1ωnkij=0n1ajωnij=1ni=0n1aij=0n1ωnkiωnij=1ni=0n1aij=0n1ωni(jk) \begin{aligned} \frac{1}{n} \sum_{i = 0}^{n - 1} b_i \cdot \omega_n^{-ki} &= \frac{1}{n} \sum_{i = 0}^{n - 1}\omega_n^{-ki} \sum_{j = 0}^{n - 1} a_j \cdot \omega_n^{ij}\\ &= \frac{1}{n} \sum_{i = 0}^{n - 1} a_i \sum_{j = 0}^{n - 1}\omega_n^{-ki} \cdot \omega_n^{ij}\\ &= \frac{1}{n} \sum_{i = 0}^{n - 1} a_i \sum_{j = 0}^{n - 1}\omega_n^{i(j - k)} \end{aligned}

对于j=kj = k,有

j=0n1ωni(jk)=n \sum_{j = 0}^{n - 1} \omega_n^{i(j - k)} = n

对于jkj\not = k,因为0j,k<n0 \leq j,k < n,所以0<jk<n0 < |j - k| < n,所以ωnjk1\omega_n^{j - k} \not = 1 ,根据等比数列求和公式得到

j=0n1ωni(jk)=j=0n1(ωnjk)i=1(ωnjk)n1ωjk=1(ωnn)jk1ωjk=0 \begin{aligned} \sum_{j = 0}^{n - 1} \omega_n^{i(j - k)} &= \sum_{j = 0}^{n - 1} (\omega_n^{j - k})^i\\ &= \frac{1 - (\omega_n^{j - k})^n}{1 - \omega^{j - k}}\\ &= \frac{1 - (\omega_n^n)^{j - k}}{1-\omega^{j - k}}\\ &= 0 \end{aligned}

于是考虑原式

1ni=0n1bij=0n1ωni(jk)=ai \frac{1}{n} \sum_{i = 0}^{n - 1} b_i \sum_{j = 0}^{n - 1}\omega_n^{i(j - k)} = a_i

蝴蝶操作

时间复杂度为O(nlogn)O(n \log n)

n=2mn = 2m,我们有一个这样的变换

A(x)=A0(x2)+xA1(x2)A0(x)=i=0m1a2iωniA1(x)=i=0m1a2i+1ωni \begin{aligned} \mathbf A(x) &= \mathbf {A_0}(x^2) + x\mathbf {A_1}(x^2)\\ \mathbf {A_0}(x) &= \sum_{i = 0}^{m - 1} a_{2i} \cdot \omega_n^i\\ \mathbf {A_1}(x) &= \sum_{i = 0}^{m - 1} a_{2i + 1} \cdot \omega_n^i \end{aligned}

对于0k<n0 \leq k < nx=ωnkx = \omega_n^k

A(ωnk)=A0(ωmk)+ωnkA1(ωmk)A(ωnk+m)=A0(ωmk)ωnkA1(ωmk) \begin{aligned} \mathbf A(\omega_n^k) &= \mathbf A_0(\omega_m^k) + \omega_n^k \mathbf A_1(\omega_m^k)\\ \mathbf A(\omega_n^{k + m}) &= \mathbf A_0(\omega_m^k) - \omega_n^k\mathbf A_1(\omega_m^k) \end{aligned}

这两个结果只有常数项不同,那么可以同时计算两个式子的结果

位逆序置换

多项式学习笔记

我们可以发现,如果每次都对奇偶分离的话,最终我们要处理的顺序,是对于顺序排列的二进制反着输出一遍的结果

for (int i = 0; i < maxn; i++) {
    rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << len - 1);
}

我们把序列a\mathbf arev\mathbf {rev}的值的顺序排序得到a\mathbf a'

FFT实现

maxnmaxn是第一个大于等于nn且是22的整次幂的数

我们对a\mathbf a'中每一对相邻的长度为2k2^k的序列进行蝴蝶操作,其中12k<maxn1 \leq 2^k < maxn,就实现了FFT

观察DFT和DFT的逆变换的式子

bk=i=0n1aiωnkiak=1ni=0n1biωnki \begin{aligned} b_k &= \sum_{i = 0}^{n - 1} a_i \cdot \omega_n^{ki}\\ a_k &= \frac{1}{n} \sum_{i = 0}^{n - 1} b_i \cdot \omega_n^{-ki} \end{aligned}

不同点仅两处常数,所以对于FFT和IFFT我们带个参数,实现方法就一致了

void init() {
    for (int i = 1; i < maxn; i <<= 1) {
        for (int j = 0; j < i; j++) {
            w[i + j] = (node){cos(pi * j / i), sin(pi * j / i)};
        }
    }
}
void fft(node *a, int opt) {
    for (int i = 0; i < maxn; i++) {
        if (i < rev[i]) swap(a[i], a[rev[i]]);
    }
    for (int i = 1; i < maxn; i <<= 1) {
        for (int j = 0; j < maxn; j += i << 1) {
            for (int k = 0; k < i; k++) {
                node x = a[j + k], t = (node){w[i + k].x, w[i + k].y * opt} * a[i + j + k];
                a[j + k] = x + t;
                a[i + j + k] = x - t;
            }
        }
    }
}

考虑核心部分的复杂度,长度为2k2^k的序列共有logn\log n种,我们当作logn\log n层,每一层我们要计算n/2n/2次,总的复杂度就是O(nlogn)O(n \log n)

原根

ggpp的原根,当且仅当gk,0kp2g^k,0 \leq k \leq p-2mod  p\mod p意义下均不相同

pp是质数时,原根一定存在

mod  p\mod p意义下,gg可以说是一个p1p - 1次本原单位根

数论变换 NTT

考虑到FFT有精度问题,但我们可以通过利用原根的不同次方实现FFT,我们可以把FFT过程中的ωnk\omega_n^k替换为gnkg_n^k并不断取模

因为gnkg_n^k对于0k<n0 \leq k < n有不同的取值,所以上述方案是可行的,也就是ggmod  p\mod p意义下有本原单位根的性质

重点在于这个模数,我们必须选一个质数ppp1p - 1要含有22的幂的因子,并且这个22的幂要大于多项式系数个数nn,通常选择形如a2k+1a\cdot 2^k + 1的素数

常见的模数有UOJ模数

998244353=7×17×223+1 998244353 = 7 \times 17 \times 2^{23} + 1

还有

1004535809=479×221+1 1004535809 = 479 \times 2^{21} + 1

上面两个数的最小正原根都是33

相关标签: OI学习笔记