基础多项式算法

Posted by Panda2134's Blog on July 1, 2018

约定:设 $\deg(A(x))$ 为多项式 $A(x)$ 的次数。

FFT 和 NTT

可以参见 Miskcoo’s Blog

多项式求逆

定义: 对于 $n$ 次多项式 $A(x)$ ,求出多项式 $B(x)$ ,满足 $A(x) \times B(x) \equiv 1 \pmod{x^p}$.

用数学归纳法的思路。

在 $ \text{mod } x $ 的意义下, $A(x)$ 的逆多项式为 ${a_0}^{-1}$ . 也就是说,要能多项式求逆,首先常数项必须存在逆。在 $\bmod 998244353$ 意义下这等价于 $a_0 \ne 0$.

假定我们已经知道了 $A(x)$ 在 $\text{mod }x^t$ 意义下的逆 $B_0(x)$ ,想要求它在 $\text{mod }x^{2t}$ 意义下的逆 $B(x)$.

由逆多项式定义, $A(x) \times B(x) \equiv 1 \pmod{x^{2t}}$ ,也就是 $1 \dots x^{2t-1}$ 次项系数都是 $0$ ,只有常数项是 $1$ .

那么显然就有

\[A(x) \times B(x) \equiv 1 \pmod{x^{t}}\]

而由定义又有

\[A(x) \times B_0(x) \equiv 1 \pmod{x^{t}}\]

两式相减,得

\[A(x) \times [B(x)-B_0(x)] \equiv 0 \pmod{x^{t}}\]

显然 $A(x) \ne 0$ ,否则逆多项式不存在。于是一定有

\[B(x) - B_0(x) \equiv 0 \pmod{x^{t}}\]

这等价于 $x^t \mid [B(x) - B0(x)]$ .

平方后,有

\[x^{2t} \mid [B(x) - B_0(x)]^2 \Leftrightarrow [B(x) - B_0(x)]^2 \equiv 0 \pmod{x^{2t}} \\ B(x)^2 \equiv 2B(x) \times B_0(x) - B_0(x)^2 \pmod{x^{2t}}\]

同乘 $A(x)$ 后: \(B(x) \equiv 2B_0(x) - B_0(x)^2 \times A(x) \pmod{x^{2t}}\)

稍微变形一下,就是: \(A(x) \times B_0(x) \equiv 1 \pmod{ x^{\lceil \frac{p}{2} \rceil}} \\ \Rightarrow B(x) \equiv B_0(x)\times(2 - A(x) \times B_0(x) ) \pmod{x^{p}}\)

倍增即可。(想一想,为什么是上取整)

Code:

void poly_inv(int deg, int A[], int B[]) { // mod x^{deg},调用前要保证B[]为空 
    static int tmp[MAXN+10];
    if(deg == 1) {
        B[0] = mod_inv(A[0]);
    } else {
        poly_inv((deg + 1) >> 1, A, B);
        int len;
        for(len = 1; len < 2 * deg; len <<= 1); // 注意:相乘后次数>=n,所以开2倍
        copy(A, A+deg, tmp);
        fill(tmp+deg, tmp+len, 0);
        NTT(len, tmp); NTT(len, B);
        for(int i = 0; i < len; i++)
            B[i] = mul(B[i], sub(2, mul(tmp[i], B[i])));
        NTT(len, B, true);
        fill(B+deg, B+len, 0);
    }
}

多项式除法 / 多项式取模

定义:对于 $n$ 次多项式 $A(x)$ 和 $m$ 次多项式 $B(x)$ ,求出 $n-m$ 次多项式 $Q(x)$ 和小于 $m$ 次的多项式 $R(x)$,使得它们满足下列关系: \(A(x) = Q(x) \times B(x) + R(x)\)

思路是考虑用模去掉余数的影响。

取 $A(x)​$ 的系数翻转 $A^R(x) = x^nA(\frac{1}{x})​$ ,于是:

\[\begin{align*} A^R(x) &= x^n \times Q(\frac{1}{x}) \times B(\frac{1}{x}) + x^n \times R(\frac{1}{x}) \\ &= \left(x^{n-m}Q(\frac{1}{x})\right) \times \left(x^mB(\frac{1}{x})\right) + x^n \times R(\frac{1}{x}) \\ &= Q^R(x) \times B^R(x) + x^n \times R(\frac{1}{x}) \end{align*}\]

显然 $x^n \times R(1/x)$ 的系数非 $0$ 项次数在 $[n-m+1, n] \cap \mathbb{Z}$ 内。于是放在 $\bmod{x^{n-m+1}}$ 意义下即可消除其影响。

\[\begin{align*} A^R(x) &\equiv Q^R(x) \times B^R(x) &\pmod{x^{n-m+1}} \\ Q^R(x) &\equiv A^R(x) \times \left(B^R(x)\right)^{-1} &\pmod{x^{n-m+1}} \end{align*}\]

刚好 $Q^R(x)$ 的次数小于 $n-m+1$ ,故可以完整地求出 $Q^R(x)$ ,进而求出 $Q(x)$,剩下的也好办了。

Code:

void poly_sub(int deg, int A[], int B[], int C[]) {
    for(int i = 0; i <= deg; i++) C[i] = sub(A[i], B[i]);
}

void poly_div(int n, int m, int A0[], int B0[], int Q[], int R[]) {
    static int A[MAXN+10], B[MAXN+10], tmp[MAXN+10];
    CLEAR(A); CLEAR(B); CLEAR(tmp);
    copy(A0, A0+n+1, A); copy(B0, B0+m+1, B);
    reverse(A, A+n+1); reverse(B, B+m+1);
    poly_inv(n-m+1, B, tmp);
    conv(n-m, A, tmp, Q);
    fill(Q+n-m+1, Q+n+1, 0);
    reverse(Q, Q+n-m+1);
    copy(A0, A0+n+1, A); copy(B0, B0+m+1, B);
    CLEAR(tmp);
    conv(n, B, Q, tmp);
    poly_sub(n, A, tmp, R);
    fill(R+m, R+n+1, 0);// n+1! past-end iterator! 
}

多项式牛顿迭代

数域为 $\mathbb{R}$ 的牛顿迭代

用途:求解函数 $f(x)$ 的零点。

我们考虑泰勒展开:

\[g(x) = \sum_{i=0}^{\infty}\frac{f^{(i)}(x_0)}{i!} (x-x_0)^i\]

取前 $2$ 项来拟合 $f(x)$,求其零点,即为:

\[g(x) \approx f(x_0) + f'(x_0)\cdot(x-x_0) = 0\Rightarrow x = x_0 - \frac{f(x_0)}{f'(x_0)}\]

不断迭代即可接近零点。

多项式环上的牛顿迭代

用途:已知 $\deg(G(x)) = t$. 求解满足下列方程的 $F(x)$:

\[G(F(x)) \equiv 0 \pmod{x^n}\]

考虑类似上文的迭代,每次精度翻倍,即增加了一倍的系数(对于高次项)

当 $n = 1$ 时必须有 $G(F(x)) \equiv 0 \pmod{x}$,这是数学归纳法的基础。

假设我们已知 $F_0(x)$ ,且 $\deg F_0(x) = \left\lceil\frac{n}{2}\right\rceil$. 考虑在 $F_0(x)$ 处对 $G(x)$ 进行泰勒展开:

\[0 = G(F_0(x)) + G'(F_0(x)) \cdot (F(x)-F_0(x)) + \frac{G''(F_0(x))}{2} \cdot \left(F(x)-F_0(x)\right) ^2 + \cdots\]

注意 $\left(F(x) - F_0(x)\right)^2$ 这一项:由于 $F(x) \equiv F_0(x) \pmod{x^{\lceil n/2 \rceil}}$ ,在 $\bmod{x^n}$ 意义下,$F(x)-F_0(x)$ 系数非 $0$ 项次数属于 $\left[\lceil n/2 \rceil, n\right] \cap \mathbb{Z}$.

于是 $\left(F(x) - F_0(x)\right)^2$ 的非 $0$ 项中,最低的一项次数 $\ge n$. 故它即之后的项对答案没有贡献。

故最终的式子是:

\[F(x) = F_0(x) - \frac{G(F_0(x))}{G'(F_0(x)))}\]

多项式 $\ln$

定义:求出多项式 $F(x)$ ,满足 $F(x) \equiv \ln G(x) \pmod{x^n}$.

这里我们不需要采用多项式算法,转而使用微积分解决。

\[\begin{align*} \frac{\text{d}}{\text{d}x} \ln G(x) &= \frac{1}{G(x)} \cdot \frac{\text{d}}{\text{d}x} G(x) \\ \int \text{d}\ln G(x) &= \int \frac{1}{G(x)} \cdot \text{d} G(x) \\ \ln G(x) &= \int \frac{G'(x)}{G(x)} \text{d}x \end{align*}\]

其中 $\frac{G’(x)}{G(x)} = G’(x)\times G^{-1}(x)$,用多项式求逆解决。下面的问题就是如何实现多项式求导和积分。

求导:

\[\frac{\text{d}}{\text{d}x}\sum_{i=0}^n a_ix^i = \sum_{i=0}^{n-1}(i+1) \cdot a_{i+1}x^i\]

积分:

\[\int \sum_{i=0}^n a_i x^i = \sum_{i=1}^{n+1} \frac{a_{i-1}}{i} x^i\]

这些都可以 $O(n)$ 实现。

Code:

void poly_int(int deg, int A[], int B[]) {
    B[0] = 0;
    for(int i = 1; i <= deg + 1; i++)
        B[i] = mul(A[i - 1], mod_inv(i));
}

void poly_d(int deg, int A[], int B[]) {
    for(int i = 0; i <= deg - 1; i++)
        B[i] = mul(i + 1, A[i + 1]);
    B[deg] = 0;
}

void poly_ln(int deg, int A0[], int B[]) { // mod x^{deg}
    static int A[MAXN + 10], A1[MAXN + 10], tmp[MAXN + 10];
    fill(A, A + MAXN, 0); fill(A1, A1 + MAXN, 0); fill(tmp, tmp + MAXN, 0);
    copy(A0, A0+deg, A);
    poly_d(deg - 1, A, A1);
    poly_inv(deg, A, tmp);
    copy(tmp, tmp + deg, A);
    conv(deg-1, A, A1, tmp);
    poly_int(deg-1, tmp, B);
}

多项式 $\exp$

定义:求出多项式 $F(x)$,满足 $F(x)\equiv e^{G(x)} \pmod{x^n} \pmod{x^n}$.

变形为 $\ln F(x) - G(x) = 0$ ,然后直接套用多项式牛顿迭代,则有:

\[F(x) = F_0(x) + (G(x)-\ln F_0(x)) \times F_0(x)\]

使用多项式 $\ln$ 计算 $\ln F_0(x)$ 即可。

Code:

void poly_exp(int deg, int A[], int B[]) { // mod x^{deg}
    static int tmp[MAXN + 10];
    fill(tmp, tmp + MAXN, 0);
    if(deg == 1) {
        B[0] = 1;
    } else {
        int half = (deg + 1) >> 1;
        poly_exp(half, A, B);
        fill(B+half, B+deg, 0);

        poly_ln(deg, B, tmp);
        poly_sub(deg - 1, A, tmp, tmp);
        conv(deg - 1, tmp, B, tmp);
        poly_add(deg - 1, B, tmp, B);
    }
}

多项式开方 / 多项式任意次幂

定义:求出多项式 $F(x)$,满足 $F(x) \equiv {G(x)}^k \pmod{x^n}$.

特殊地,定义多项式开方为 $k=\dfrac{1}{2}$,即 $F(x) \equiv \sqrt{G(x)}$ 的情形。

显然有 $F(x) \equiv \exp(k\ln(G(x)))$,于是应用多项式 $\ln$ 和多项式 $\exp$ 即可解决。

多项式模板大全

#include <bits/stdc++.h>
using namespace std;

namespace polynomial {
    const int MAXN = 100000 << 2, MOD = 998244353, G = 3;
    int p_omega[MAXN + 10], p_inv[MAXN + 10];

    inline int pls(int a, int b) {
        int ret = (a + b);
        if(ret >= MOD) ret -= MOD;
        return ret;
    }

    inline int dec(int a, int b) {
        int ret = (a - b);
        if(ret < 0) ret += MOD;
        return ret;
    }

    inline int mul(int a, int b) { return (1ll * a) * b % MOD; }

    inline int fpow(int a, int x) {
        int ret = 1;
        while(x) {
            if(x & 1) ret = mul(ret, a);
            x >>= 1;
            a = mul(a, a);
        }
        return ret;
    }

    inline int mod_inv(int x) { return fpow(x, MOD - 2); }

    //-------------------NTT----------------------

    void init_omega() {
        for(int step = 1; step <= MAXN; step <<= 1) {
            p_omega[step] = fpow(G, (MOD - 1) / step);
            p_inv[step] = fpow(G, (MOD - 1) / step * (step - 1));
        }
    }

    struct _omega_init_t {
        _omega_init_t() { init_omega(); }
    } _omega_init;

    inline int get_rt(int step, bool idft) {
        return !idft ? p_omega[step] : p_inv[step];
    }

    inline void NTT(int len, int A[], bool idft = false) {
        static int rev[MAXN + 10];
        for(int i = 0; i < len; i++)
            rev[i] = (((rev[i >> 1] >> 1) | (len >> (i & 1))) & (len - 1));
        for(int i = 0; i < len; i++)
            if(rev[i] > i) swap(A[i], A[rev[i]]);

        for(int step = 1; step < len; step <<= 1) {
            int rt = get_rt(step << 1, idft);
            for(int i = 0; i < len; i += (step << 1)) {
                for(int j = 0, omega = 1; j < step;
                    j++, omega = mul(omega, rt)) {
                    int t = mul(A[i + j + step], omega);
                    A[i + j + step] = dec(A[i + j], t);
                    A[i + j] = pls(A[i + j], t);
                }
            }
        }

        if(idft) {
            int t = mod_inv(len);
            for(int i = 0; i < len; i++) A[i] = mul(A[i], t);
        }
    }

    inline void conv(int deg, int A0[], int B0[], int C[]) {
        static int A[MAXN + 10], B[MAXN + 10];
        int len;
        copy(A0, A0+deg+1, A), copy(B0, B0+deg+1, B);
        for(len = 1; len < deg * 2 + 1; len <<= 1);
        fill(A+deg+1, A+len, 0), fill(B+deg+1, B+len, 0); // !!
        NTT(len, A), NTT(len, B);
        for(int i = 0; i < len; i++) C[i] = mul(A[i], B[i]);
        NTT(len, C, true);
    }
    //-------------------Polynomial Add & Substract----------

    void poly_add(int deg, int A[], int B[], int C[]) {
        for(int i = 0; i <= deg; i++) C[i] = pls(A[i], B[i]);
    }

    void poly_sub(int deg, int A[], int B[], int C[]) {
        for(int i = 0; i <= deg; i++) C[i] = dec(A[i], B[i]);
    }

    //------------Polynomial Integral & Derivative-----------

    void poly_int(int deg, int A[], int B[]) {
        B[0] = 0;
        for(int i = 1; i <= deg + 1; i++)
            B[i] = mul(A[i - 1], mod_inv(i));
    }

    void poly_d(int deg, int A[], int B[]) {
        for(int i = 0; i <= deg - 1; i++)
            B[i] = mul(i + 1, A[i + 1]);
        B[deg] = 0;
    }

    //-------------------Polynomial inverse------------------

    void poly_inv(int deg, int A[], int B[]) { // mod x^{deg}
        int tmp[MAXN + 10];
        if(deg == 1) {
            assert(A[0] != 0);
            B[0] = mod_inv(A[0]);
        } else {
            poly_inv((deg + 1) >> 1, A, B);
            int len;
            for(len = 1; len < 2 * deg; len <<= 1); // !!
            copy(A, A+deg, tmp); fill(tmp+deg, tmp+len, 0);
            NTT(len, tmp); NTT(len, B);
            for(int i = 0; i < len; i++)
                B[i] = mul(B[i], dec(2, mul(tmp[i], B[i])));
            NTT(len, B, true);
            fill(B+deg, B+len, 0); // !!
        }
    }

    //--------------------Polynomial division-----------------

    void poly_div(int n, int m, int A0[], int B0[], int Q[], int R[]) { // mod x^{deg}
        int A[MAXN + 10], B[MAXN + 10], tmp[MAXN + 10];
        fill(A, A+MAXN, 0); fill(B, B+MAXN, 0); fill(tmp, tmp+MAXN, 0);
        copy(A0, A0+n+1, A), copy(B0, B0+m+1, B);
        reverse(A, A+n+1), reverse(B, B+m+1);
        poly_inv(n-m+1, B, tmp);
        conv(n-m, A, tmp, Q);
        fill(Q+n-m+1, Q+n+1, 0);
        reverse(Q, Q+n-m+1);
        copy(A0, A0+n+1, A), copy(B0, B0+m+1, B);
        conv(n, B, Q, tmp);
        poly_sub(n, A, tmp, R);
        fill(R+m, R+n+1, 0);
    }

    //--------------------Polynomial ln------------------------

    void poly_ln(int deg, int A0[], int B[]) { // mod x^{deg}
        static int A[MAXN + 10], A1[MAXN + 10], tmp[MAXN + 10];
        fill(A, A + MAXN, 0); fill(A1, A1 + MAXN, 0); fill(tmp, tmp + MAXN, 0);
        copy(A0, A0+deg, A);
        poly_d(deg - 1, A, A1);
        poly_inv(deg, A, tmp);
        copy(tmp, tmp + deg, A);
        conv(deg-1, A, A1, tmp);
        poly_int(deg-1, tmp, B);
    }

    //--------------------Polynomial exp-----------------------

    void poly_exp(int deg, int A[], int B[]) { // mod x^{deg}
        static int tmp[MAXN + 10];
        fill(tmp, tmp + MAXN, 0);
        if(deg == 1) {
            B[0] = 1;
        } else {
            int half = (deg + 1) >> 1;
            poly_exp(half, A, B);
            fill(B+half, B+deg, 0);

            poly_ln(deg, B, tmp);
            poly_sub(deg - 1, A, tmp, tmp);
            conv(deg - 1, tmp, B, tmp);
            poly_add(deg - 1, B, tmp, B);
        }
    }
}