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.
| Operation | Naive | Fast | Method |
|---|---|---|---|
| add, scalar multiply, derivative, integral | coefficientwise | ||
| multiply | NTT | ||
| divide by ; evaluate at one point | synthetic division = Horner | ||
| inverse, log, exp, sqrt, power mod | Newton iteration | ||
| divide with remainder | reversal + inverse | ||
| divide and conquer | |||
| evaluate at / interpolate from points | product tree (Evaluation and Interpolation) | ||
| gcd | half-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.
| Series | Newton step / formula | Requirement |
|---|---|---|
| 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));
}Proof: Newton iteration and each formula
Quadratic convergence. Suppose with invertible and . Taylor expansion gives , and , so . Inverse: gives ; the base needs . Log: and has constant term for , so integrate; the integral divides by , fine for (the linear-time inverses use ). Exp: , , giving . Sqrt: gives ; a series with lowest term has a square root iff is even and is a square (then ), and
sqrtMintis Tonelli–Shanks (Modular Square Roots). Power: with , so ; the coefficient of in is a polynomial in of degree whose denominators divide (coefficients of -type terms), so for only matters, while uses the true . Costs: each step does multiplications of length (exp also a log), so the totals are .Division. For with , , reversing () gives , so ( has constant term ), and makes this determine ; then has degree . Synthetic division / Horner: , and matching coefficients from the top gives , the same recurrence as Horner’s evaluation. Product tree: . All functions were checked against series arithmetic (and , , , long division) for random inputs.
| Root / factor task | Method |
|---|---|
| rational roots over | , (rational root theorem) |
| roots modulo a prime | , then Cantor–Zassenhaus (Factorization) |
| real roots | Sturm sequences + bisection (Roots and Integration) |
| all complex roots | Durand–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 |
| Problem | Method |
|---|---|
| objects with a recursive structure | solve the functional equation, extract coefficients (Newton or online convolution) |
| set partitions, permutations by cycles | EGF + / |
| labelled connected graphs | of (Graph Counting) |
| linear recurrence | rational 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.
Related notes
- NTT (
polyMul) - Generating Functions
- Online Convolution (when the series is defined by its own prefix)