Polynomial arithmetic and formal power series modulo : inverse, logarithm, exponential, square root, power, division with remainder, all by Newton’s method on top of polyMul from NTT (modulus 998244353). This block is the second half of the chapter’s shared core; evaluation, composition, recurrences and counting build on it.

OperationNaiveFastMethod
add, scalar multiply, derivative, integralcoefficientwise
multiplyNTT
divide by ; evaluate at one pointsynthetic division = Horner
inverse, log, exp, sqrt, power mod Newton iteration
divide with remainderreversal + inverse
divide and conquer
evaluate at / interpolate from pointsproduct tree (Evaluation and Interpolation)
gcdhalf-gcd (GCD and Resultant)
composition Kinoshita–Li / Brent–Kung (Composition)
shift one convolution with factorials

Newton’s method is the master technique: to solve , iterate , doubling the number of correct coefficients each step; the last doubling dominates, . Every operation below is an instance.

SeriesNewton step / formulaRequirement
lowest non-zero term with even and a quadratic residue
, any only enters the exponential; zero if
Poly polyTrunc(Poly a, int n) { a.resize(n); return a; }
Poly mulTrunc(const Poly& a, const Poly& b, int n) { Poly c = polyMul(polyTrunc(a, min(sz(a), n)), polyTrunc(b, min(sz(b), n))); c.resize(n); return c; }
Poly derivative(const Poly& a) { Poly r(max(sz(a) - 1, 0)); for (int i = 1; i < sz(a); i++) r[i - 1] = a[i] * mint(i); return r; }
Poly integrate(const Poly& a) {
    int n = sz(a); Poly r(n + 1, 0); vector<mint> iv(n + 2, 1);
    for (int i = 2; i <= n; i++) iv[i] = -mint(998244353 / i) * iv[998244353 % i];   // 1/i, linear
    for (int i = 0; i < n; i++) r[i + 1] = a[i] * iv[i + 1];
    return r;
}
Poly polyInv(const Poly& a, int n) {                       // a[0] != 0
    Poly b{a[0].inv()};
    for (int k = 1; k < n; k *= 2) {
        Poly t = mulTrunc(a, b, 2 * k); for (auto& x : t) x = -x; t[0] += 2;
        b = mulTrunc(b, t, 2 * k);
    }
    b.resize(n); return b;
}
Poly polyLog(const Poly& a, int n) {                       // a[0] == 1
    if (n == 0) return {};
    Poly r = integrate(mulTrunc(derivative(a), polyInv(a, n), n - 1)); r.resize(n); return r;
}
Poly polyExp(const Poly& a, int n) {                       // a[0] == 0
    Poly b{1};
    for (int k = 1; k < n; k *= 2) {
        Poly t = polyLog(b, 2 * k); for (int i = 0; i < 2 * k; i++) t[i] = (i < sz(a) ? a[i] : mint(0)) - t[i]; t[0] += 1;
        b = mulTrunc(b, t, 2 * k);
    }
    b.resize(n); return b;
}
bool sqrtMint(mint a, mint& r) {                           // Tonelli-Shanks for 998244353
    const ll p = 998244353;
    if (a == mint(0)) { r = 0; return true; }
    if (a.pow((p - 1) / 2) != mint(1)) return false;
    ll q = p - 1; int s = 0; while (q % 2 == 0) q /= 2, s++;
    mint c = mint(3).pow(q), t = a.pow(q), x = a.pow((q + 1) / 2); int m = s;   // 3 is a non-residue
    while (t != mint(1)) {
        int i = 0; mint tt = t; while (tt != mint(1)) tt *= tt, i++;
        mint b = c; for (int j = 0; j < m - i - 1; j++) b *= b;
        x *= b; c = b * b; t *= c; m = i;
    }
    r = x; return true;
}
Poly polySqrt(const Poly& a, int n) {                       // {} if no square root exists
    int t = 0; while (t < sz(a) && a[t] == mint(0)) t++;
    if (t == sz(a) || t >= n) return Poly(n, 0);            // B = 0 works
    if (t % 2) return {};
    Poly b(a.begin() + t, a.end()); mint s0; if (!sqrtMint(b[0], s0)) return {};
    int m = n - t / 2; Poly r{s0}; mint inv2 = mint(2).inv();
    for (int k = 1; k < m; k *= 2) {
        Poly q = mulTrunc(b, polyInv(r, 2 * k), 2 * k); r.resize(2 * k);
        for (int i = 0; i < 2 * k; i++) r[i] = (r[i] + q[i]) * inv2;
    }
    r.resize(m); Poly res(n, 0); for (int i = 0; i < m; i++) res[i + t / 2] = r[i];
    return res;
}
Poly polyPow(const Poly& a, ll k, int n) {                 // a^k mod x^n, k >= 0 (huge allowed)
    if (k == 0) { Poly r(n, 0); if (n) r[0] = 1; return r; }
    int t = 0; while (t < sz(a) && a[t] == mint(0)) t++;
    if (t == sz(a) || (i128)t * k >= n) return Poly(n, 0);
    int m = n - t * k; mint c = a[t], ci = c.inv();
    Poly b(min(sz(a) - t, m)); for (int i = 0; i < sz(b); i++) b[i] = a[i + t] * ci;
    Poly l = polyLog(b, m); for (auto& x : l) x *= mint(k % 998244353);
    Poly e = polyExp(l, m); mint ck = c.pow(k);
    Poly res(n, 0); for (int i = 0; i < m; i++) res[i + t * k] = e[i] * ck;
    return res;
}
pair<Poly, Poly> polyDivMod(Poly a, Poly b) {               // a = q b + r, deg r < deg b; b non-zero
    while (!b.empty() && b.back() == mint(0)) b.pop_back();
    while (!a.empty() && a.back() == mint(0)) a.pop_back();
    int d = sz(a) - sz(b);
    if (d < 0) return {{}, a};
    Poly ra(a.rbegin(), a.rend()), rb(b.rbegin(), b.rend());
    Poly q = mulTrunc(ra, polyInv(rb, d + 1), d + 1); reverse(all(q));
    Poly qb = polyMul(q, b), r(sz(b) - 1);
    for (int i = 0; i < sz(r); i++) r[i] = a[i] - qb[i];
    while (!r.empty() && r.back() == mint(0)) r.pop_back();
    return {q, r};
}
mint evalPoly(const Poly& a, mint x) { mint r = 0; for (int i = sz(a) - 1; i >= 0; i--) r = r * x + a[i]; return r; }   // Horner
Poly divLinear(Poly p, mint a, mint& rem) {                 // p / (x - a); rem = p(a)
    if (p.empty()) { rem = 0; return {}; }
    for (int i = sz(p) - 2; i >= 0; i--) p[i] += a * p[i + 1];
    rem = p[0]; p.erase(p.begin()); return p;
}
Poly productOfLinear(const vector<mint>& r, int lo, int hi) {   // prod_{lo <= i < hi} (x - r_i), O(n log^2 n)
    if (hi - lo == 0) return {1};
    if (hi - lo == 1) return {-r[lo], 1};
    int mid = (lo + hi) / 2;
    return polyMul(productOfLinear(r, lo, mid), productOfLinear(r, mid, hi));
}
Root / factor taskMethod
rational roots over , (rational root theorem)
roots modulo a prime, then Cantor–Zassenhaus (Factorization)
real rootsSturm sequences + bisection (Roots and Integration)
all complex rootsDurand–Kerner, or companion-matrix eigenvalues
squarefree part
factorisation over / SFF DDF EDF / mod , Hensel lifting, recombination

Identities: (over a domain), , the remainder theorem , and Vieta: the coefficients of are the elementary symmetric polynomials of the . Representation: vector<mint>, index = degree; trim trailing zeros where degrees matter (division, gcd).

EGFs and the standard series

Series operation (EGF, )Combinatorial meaning
split a labelled set into two labelled parts
a set of -structures (exponential formula)
a sequence of -structures
recover connected structures from all structures
/ mark and remove one element / add a distinguished element


ProblemMethod
objects with a recursive structuresolve the functional equation, extract coefficients (Newton or online convolution)
set partitions, permutations by cyclesEGF + /
labelled connected graphs of (Graph Counting)
linear recurrencerational GF, Bostan–Mori (Linear Recurrences)
binomial transform = multiply the EGF by
“at least one of each”inclusion–exclusion, or
integer partitions via (Counting with Power Series)

The exponential formula ( = connected structures, = all structures) turns “count connected graphs” into “count all graphs, take a log”. Practical notes: everything is modulo (truncate aggressively; mulTrunc saves about half); precompute factorials for OGF/EGF conversions; normalise before calling (inv needs , log , exp ); use 998244353 so NTT applies directly.