多项式全家桶

以下若无特殊说明,函数均为多项式函数。系数对 998244353 取模。

多项式乘法#

FFT——从入门到入土:6. 快速数论变换

多项式牛顿迭代#

设有任意可泰勒展开的函数 g,求满足 g(f(x))0(modxn) 的多项式 f(x),此时可以使用牛顿迭代法求解。

具体的讲,牛顿迭代法是有以下过程:

  1. 首先求出 n=1 时的 f(x)

  2. 假设我们已经求出了 g[f(x)]0(modxn) 的解 f0(x),则 g[f(x)]0(modx2n) 的解为

    f(x)f0(x)g[f0(x)]g[f0(x)](modx2n)

    证:
    我们直接在 f(x)=f0(x) 处对 g 泰勒展开:

    g[f(x)]i0g(i)[f0(x)]i![f(x)f0(x)]i(modx2n)

    考虑到 f(x)f0(x)(modxn),我们有 i2,[f(x)f0(x)]i0(modx2n)

    因此又有:

    g[f(x)]g[f0(x)]+g[f0(x)][f(x)f0(x)]0(modx2n)f(x)f0(x)g[f0(x)]g[f0(x)](modx2n)

时间复杂度:T(n)=T(n2)+f(n),注意递归时 nn2

关于求导:将 h 看作一次自变量,f 看作常数即可,由于待求值为 h 而不是 x 因此本质应为求偏导(或形式幂级数)一类的方法,这里不做深究。

多项式求逆#

求满足 h(x)f(x)1(modxn)h(x)

由题意,1h(x)f(x)(modxn),考虑使用牛顿迭代,构造函数 g(h(x))=1h(x)f(x),即求解 g[h(x)]0(modxn)

  1. n=1 时,h(x)inv([x0]f(x))(显而易见的,[x0]f(x)=0 时不存在逆元)。

  2. n2 时,

    h(x)h0(x)g[h0(x)]g(h0(x))(modxn)h0(x)h01(x)f(x)h02(x)(modxn)2h0(x)h02(x)f(x)(modxn)

多项式 ln#

求满足 h(x)lnf(x)(modxn)h(x)

考虑求导再积分:

h(x)lnf(x)f(x)(modxn)f1(x)f(x)(modxn)h(x)f(x)f(x)dx+C(modxn)

x=0,可以求出 C=[x0]lnf(x)=h(0)=lnf(0)

由于 e 是超越数,因此 xQ{0},lnxQ

因此 f(0)=1,[x0]h(x)=h(0)=0

多项式 exp#

求满足 h(x)ef(x)(modxn)h(x)

由于 lnh(x)=f(x),使用牛顿迭代法构造 g(h(x))=lnh(x)f(x)

首先由上面得 h(x)1(modxn),考虑若当前已经求出 g[h0(x)]0(modxn/2)

h(x)h0(x)lnh0(x)f(x)lnh0(x)(modxn)h0(x)lnh0(x)f(x)h01(x)(modxn)h0(x)h0(x)lnh0(x)f(x)(modxn)

多项式幂函数#

求满足 h(x)fk(x)(modxn)h(x)

首先不妨假设 [x0]f(x)=1,对于其他取值可以通过左右移位、乘系数等变换来将常数项变成 1。

利用对数函数的性质:

h(x)fk(x)explnfk(x)exp[klnf(x)](modxn)

多项式三角函数#

中学阶段学过的:

cosx=eix+eix2,sinx=eixeix2i

使用多项式 exp 直接代入即可。(注意 Z998244353i 的取值)。

多项式开方#

求满足 hk(x)f(x)(modxn)h(x)

首先假设你已经通过一些方法求出了 n=1 时的解(如二次剩余、BSGS)。

之后牛顿迭代,过程读者自证不难。

h(x)(k1)h0k(x)f(x)kh0k1(x)(modxn)

多项式除法&取余#

设有 n 项式 f(x)m 项式 g(x),求 f(x)g(x) 的商 q(x) 和余式 r(x)

考虑到若 r(x)=0,则 q(x)=f(x)g1(x),又因为 r 的次数有特殊性,所以尝试将 r(x) 利用模运算消掉。

定义 revkf 表示将 f0k 系数翻转,忽略 k 表示全部翻转,则由多项式乘法的定义不难看出 A=BCrevA=revBrevC,revk(A+B)=revkA+revkB

则我们有

revf=rev(gq+r)=revgrevq+revnr

发现 revq 最高次为 nm,而 revr 非零最低次至少为 nm+1,因此直接 modxnm+1,即变成

revfrevgrevq(modxnm+1)

其中 g 取模不受影响,使用多项式求逆即可算出 g

板子速查#

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

const int N = 1e6 + 5, MOD = 998244353, G = 3, Gi = 332748118;

namespace Cipolla { // 二次剩余
int n, p, oyds;
struct comp {
    int r, i;
    comp(int _r = 0, int _i = 0) { r = _r, i = _i; }
    comp operator*(const comp &rhs) const {
        return comp((r * rhs.r % p + i * rhs.i % p * oyds) % p,
                    (r * rhs.i % p + i * rhs.r) % p);
    }
};
int qpow(int a, int b) {
    int res = 1;
    for (; b; b >>= 1, a = a * a % p)
        if (b & 1) res = res * a % p;
    return res;
}
comp cpow(comp a, int b) {
    comp res = comp(1, 0);
    for (; b; b >>= 1, a = a * a)
        if (b & 1) res = res * a;
    return res;
}
int check(int x) {
    if (x % p == 0) return 0;
    return qpow(x, (p - 1) >> 1);
}
int work() {
    if (check(n) == p - 1) return -1;
    int a;
    do {
        a = rand() % p;
        oyds = (a * a % p - n + p) % p;
    } while (check(oyds) != p - 1);
    return cpow(comp(a, 1), p + 1 >> 1).r % p;
}
int Cip(int _n, int _p) {
    n = _n, p = _p;
    n %= p;
    if (n == 0) {
        return 0;
    }
    int x1 = (work() + p) % p, x2 = p - x1;
    return min(x1, x2);
}
} // namespace Cipolla

int inv(int a, int b = MOD - 2, int p = MOD) { // 快速幂
    int res = 1;
    for (; b; b >>= 1, a = a * a % p)
        if (b & 1) res = res * a % p;
    return res % p;
}

int init(int n) { // 预处理
    int len = 1;
    while (len < n) len <<= 1;
    return len << 1;
}
int rev[N];
void initrev(int len) {
    for (int i = 0; i < len; i++)
        rev[i] = (rev[i >> 1] >> 1) | ((i & 1) * (len >> 1));
}

void ntt(int *a, int n, int tp) { // 快速数论变换
    for (int i = 0; i < n; i++)
        if (i < rev[i]) swap(a[i], a[rev[i]]);
    for (int block = 1; block < n; block <<= 1) {
        int siz = block << 1;
        int g1 = inv((tp == 1) ? G : Gi, (MOD - 1) / siz);
        for (int l = 0, r, gn; l < n; l += siz) {
            gn = 1, r = l + block - 1;
            for (int i = l; i <= r; i++, gn = gn * g1 % MOD) {
                int tmp = a[i];
                a[i] = (tmp + gn * a[i + block] % MOD + MOD) % MOD;
                a[i + block] = (tmp - gn * a[i + block] % MOD + MOD) % MOD;
            }
        }
    }
    if (tp == 1) return;
    int orz = inv(n);
    for (int i = 0; i < n; i++) a[i] = (a[i] * orz + MOD) % MOD;
}

void polymul(int *f, int *g, int n, int m, int *tar) { // 多项式乘法
    int len = init(n + m - 1) >> 1;
    initrev(len);
    static int a[N], b[N];
    for (int i = 0; i < n; i++) a[i] = f[i];
    for (int i = 0; i < m; i++) b[i] = g[i];
    for (int i = n; i < len; i++) a[i] = 0;
    for (int i = m; i < len; i++) b[i] = 0;
    ntt(a, len, 1), ntt(b, len, 1);
    for (int i = 0; i < len; i++) tar[i] = a[i] * b[i] % MOD;
    ntt(tar, len, -1);
    for (int i = n + m - 1; i < len; i++) tar[i] = 0;
}

void polyinv(int n, int *f, int *g) { // 多项式乘法逆
    static int tmp[N];
    if (n == 1) {
        g[0] = inv(f[0]);
        return;
    }
    polyinv((n + 1) >> 1, f, g);
    polymul(f, g, n, n, tmp), polymul(tmp, g, n, n, tmp);
    for (int i = 0; i < n; i++) g[i] = (2ll * g[i] - tmp[i] + MOD) % MOD;
}

void polydiv(int *f, int *g, int n, int m, int *q, int *r) { // 多项式除法 and 取余
    static int a[N], b[N], invb[N];
    for (int i = 0; i < n; i++) a[i] = f[i];
    for (int i = 0; i < m; i++) b[i] = g[i];
    reverse(a, a + n), reverse(b, b + m);
    int siz = n - m + 1;
    polyinv(siz, b, invb);
    polymul(a, invb, n, siz, q);
    reverse(q, q + siz);
    int len = init(n);
    for (int i = siz; i < 2ll * len; i++) q[i] = 0;
    polymul(g, q, m, siz, r);
    for (int i = 0; i < m; i++) r[i] = (f[i] - r[i] + MOD) % MOD;
}

void polyderi(int *f, int n, int *g) { // 多项式求导
    for (int i = 1; i < n; i++) g[i - 1] = i * f[i] % MOD;
    g[n - 1] = 0;
}

void polyinte(int *f, int n, int *g) { // 多项式不定积分
    for (int i = 1; i < n; i++) g[i] = f[i - 1] * inv(i) % MOD;
    g[0] = 0;
}

void polyln(int *f, int n, int *g) { // 多项式 ln
    static int a[N], b[N], c[N];
    polyderi(f, n, a), polyinv(n, f, b);
    polymul(a, b, n, n, c);
    polyinte(c, n, g);
    for (int i = n; i < n * 2; i++) g[i] = 0;
}

void polyexp(int *f, int n, int *g) { // 多项式 exp
    static int tmp[N];
    if (n == 1) {
        g[0] = 1;
        return;
    }
    polyexp(f, (n + 1) >> 1, g);
    polyln(g, n, tmp);
    for (int i = 0; i < n; i++) tmp[i] = (f[i] - tmp[i] + MOD) % MOD;
    tmp[0]++;
    polymul(g, tmp, n, n, g);
    for (int i = n; i < n * 2; i++) g[i] = 0;
}

void polypow(int *f, int n, char *k, int *g) { // 多项式快速幂
    if (k[0] == '0') {
        g[0] = 1;
        for (int i = 1; i < n; i++) g[i] = 0;
        return;
    }
    int fst = n, k1 = 0, k2 = 0, k3 = 0, lenk = strlen(k); // 处理巨大恶臭指数
    for (int i = 0; i < lenk; i++) {
        int num = k[i] - '0';
        k1 = (k1 * 10 % MOD + num) % MOD;
        k2 = (k2 * 10 % (MOD - 1) + num) % (MOD - 1);
        if (k3 < n) k3 = k3 * 10 + k[i] - '0';
    }
    if (f[0] == 0 && k3 > n) {
        for (int i = 0; i < n; i++) g[i] = 0;
        return;
    }
    static int tmp[N], a[N];
    for (int i = 0; i < n; i++)
        if (f[i]) {
            fst = i;
            break;
        }
    if (k1 * fst >= n) {
        for (int i = 0; i < n; i++) g[i] = 0;
        return;
    }
    int d = f[fst], invd = inv(d), pd = inv(d, k2);
    for (int i = fst; i < n; i++) a[i - fst] = f[i] * invd % MOD;
    for (int i = n - fst; i < n; i++) a[i] = 0;
    polyln(a, n - fst, tmp);
    for (int i = 0; i < n - fst; i++) tmp[i] = tmp[i] * k1 % MOD;
    polyexp(tmp, n - fst, g + fst * k1);
    for (int i = 0; i < fst * k1; i++) g[i] = 0;
    for (int i = fst * k1; i < n * 2; i++) g[i] = g[i] * pd % MOD;
}

void polysqrt(int *f, int n, int *g) { // 多项式开根
    if (n == 1) {
        g[0] = Cipolla::Cip(f[0], MOD);
        return;
    }
    polysqrt(f, n + 1 >> 1, g);
    static int tmp[N], inv2 = inv(2);
    polyinv(n, g, tmp);
    polymul(f, tmp, n, n, tmp);
    for (int i = 0; i < n; i++) g[i] = (g[i] + tmp[i]) % MOD * inv2 % MOD;
    for (int i = n; i < n * 2; i++) g[i] = 0;
}
posted @   LewisLi  阅读(128)  评论(1编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示
主题色彩