Factor a polynomial into irreducibles. Over a finite field this is polynomial time (randomised Cantor–Zassenhaus, or deterministic Berlekamp for small fields); over it reduces to the finite-field case plus Hensel lifting and recombination (LLL makes it polynomial). The contest use is roots modulo . Uses Poly, polyMul, polyDivMod, derivative (NTT, Power Series) and polyGcd (GCD and Resultant), over with .
The pipeline over : (1) square-free factorisation with square-free, from gcds with ; (2) distinct-degree factorisation: collects exactly the irreducible factors of degree once smaller degrees are removed; (3) equal-degree factorisation: split a product of irreducibles of the same degree randomly with (for use the trace ).
| Algorithm | Type | Time | Best for |
|---|---|---|---|
| Berlekamp | deterministic | small (e.g. ); gives the number of factors first | |
| Cantor–Zassenhaus | Las Vegas | large : the standard | |
| von zur Gathen–Shoup | randomised | (baby-step giant-step DDF) | asymptotically faster DDF |
| Kaltofen–Shoup | randomised | theoretical | |
| over | mod + Hensel lift past twice the Mignotte bound + recombination (LLL: Lenstra–Lenstra–Lovász) | polynomial with LLL; exponential recombination in bad cases (Swinnerton-Dyer) |
| Test / count | Method |
|---|---|
| irreducible over (Rabin) | and for every prime |
| irreducible via Berlekamp | |
| over : Eisenstein | a prime divides all coefficients but the leading one and |
| over | irreducible mod some (with the same degree) irreducible over (converse fails) |
| cyclotomic | always irreducible over |
| squarefree | |
| monic irreducibles of degree over | (the Lyndon-word formula) |
mt19937_64 frng(7);
Poly polyModP(const Poly& a, const Poly& f) { return polyDivMod(a, f).second; }
Poly mulModP(const Poly& a, const Poly& b, const Poly& f) { return polyModP(polyMul(a, b), f); }
Poly powModP(Poly b, ll e, const Poly& f) { Poly r = {1}; b = polyModP(b, f); for (; e; e >>= 1, b = mulModP(b, b, f)) if (e & 1) r = mulModP(r, b, f); return polyModP(r, f); }
Poly polySub1(Poly t) { if (t.empty()) t = {0}; t[0] -= 1; while (!t.empty() && t.back() == mint(0)) t.pop_back(); return t; }
const ll Q = 998244353;
void equalDegreeSplit(const Poly& g, int d, vector<Poly>& out) { // g monic squarefree, all factors of degree d
int n = sz(g) - 1; if (n == 0) return;
if (n == d) { out.push_back(g); return; }
while (true) {
Poly h(n); for (auto& x : h) x = frng() % Q;
Poly cur = polyModP(h, g), prod = cur; // h^{(q^d - 1)/2} = (h h^q ... h^{q^{d-1}})^{(q-1)/2}
for (int i = 1; i < d; i++) cur = powModP(cur, Q, g), prod = mulModP(prod, cur, g);
Poly r = polyGcd(g, polySub1(powModP(prod, (Q - 1) / 2, g)));
int dr = sz(r) - 1;
if (dr > 0 && dr < n) { equalDegreeSplit(r, d, out); equalDegreeSplit(polyDivMod(g, r).first, d, out); return; }
}
}
vector<pair<Poly, int>> squareFreeDecomposition(Poly f) { // Yun, monic f, deg f < p: f = prod g_i^i
vector<pair<Poly, int>> res; Poly df = derivative(f), a = polyGcd(f, df);
if (sz(a) == 0) a = {1};
Poly b = polyDivMod(f, a).first, c = polyDivMod(df, a).first;
Poly d = c; { Poly db = derivative(b); if (sz(d) < sz(db)) d.resize(sz(db)); for (int i = 0; i < sz(db); i++) d[i] -= db[i]; }
for (int i = 1; sz(b) > 1; i++) {
Poly ai = polyGcd(b, d); if (ai.empty()) ai = b;
if (sz(ai) > 1) res.push_back({ai, i});
b = polyDivMod(b, ai).first; c = polyDivMod(d, ai).first;
Poly db = derivative(b); d = c; if (sz(d) < sz(db)) d.resize(sz(db)); for (int j = 0; j < sz(db); j++) d[j] -= db[j];
while (!d.empty() && d.back() == mint(0)) d.pop_back();
}
return res;
}
vector<pair<Poly, int>> factorModP(Poly f) { // monic irreducible factors with multiplicities (deg f < p)
while (!f.empty() && f.back() == mint(0)) f.pop_back();
vector<pair<Poly, int>> res; if (sz(f) <= 1) return res;
mint lc = f.back().inv(); for (auto& x : f) x *= lc;
for (auto [g, mult] : squareFreeDecomposition(f)) {
Poly rest = g, xq = {0, 1};
for (int d = 1; 2 * d <= sz(rest) - 1; d++) { // distinct-degree factorisation
xq = powModP(xq, Q, rest); // x^{q^d} mod rest
Poly part = polyGcd(rest, [&] { Poly t = xq; if (sz(t) < 2) t.resize(2); t[1] -= 1; while (!t.empty() && t.back() == mint(0)) t.pop_back(); return t; }());
if (sz(part) > 1) {
vector<Poly> pieces; equalDegreeSplit(part, d, pieces);
for (auto& p : pieces) res.push_back({p, mult});
rest = polyDivMod(rest, part).first; xq = polyModP(xq, rest);
}
}
if (sz(rest) > 1) res.push_back({rest, mult}); // remaining factor is irreducible
}
return res;
}
vector<mint> rootsModP(Poly f) { // distinct roots of f in F_p
while (!f.empty() && f.back() == mint(0)) f.pop_back();
if (sz(f) <= 1) return {};
Poly xp = powModP({0, 1}, Q, f); if (sz(xp) < 2) xp.resize(2); xp[1] -= 1; // x^p - x mod f
while (!xp.empty() && xp.back() == mint(0)) xp.pop_back();
Poly g = xp.empty() ? f : polyGcd(f, xp); // product of the distinct linear factors
{ mint lc = g.back().inv(); for (auto& x : g) x *= lc; }
vector<Poly> lin; equalDegreeSplit(g, 1, lin);
vector<mint> res; for (auto& l : lin) res.push_back(-l[0]);
sort(all(res), [](mint a, mint b) { return a.v < b.v; }); return res;
}
bool isIrreducible(Poly f) { // Rabin's test
while (!f.empty() && f.back() == mint(0)) f.pop_back();
int n = sz(f) - 1; if (n <= 0) return false;
mint lc = f.back().inv(); for (auto& x : f) x *= lc;
vector<int> primes; for (int m = n, r = 2; m > 1; r++) if (m % r == 0) { primes.push_back(r); while (m % r == 0) m /= r; }
auto xPowQPow = [&](int k) { Poly t = {0, 1}; for (int i = 0; i < k; i++) t = powModP(t, Q, f); return t; }; // x^{q^k} mod f
Poly full = xPowQPow(n); Poly xx = polyModP({0, 1}, f);
if (full != xx) return false;
for (int r : primes) { Poly t = xPowQPow(n / r); if (sz(t) < 2) t.resize(2); t[1] -= 1; while (!t.empty() && t.back() == mint(0)) t.pop_back(); if (t.empty() || sz(polyGcd(f, t)) > 1) return false; }
return true;
}Proof: the finite-field facts behind SFF, DDF, EDF, root finding and Rabin
. The roots of in an algebraic closure are exactly the elements of , each simple (derivative ). A monic irreducible of degree has its roots in , which lies inside iff . So . DDF: after the factors of degree are divided out of a square-free , is the product of its degree- factors; once , the rest has no factor of degree and is irreducible. SFF (Yun): for with pairwise coprime square-free in characteristic , , so ; Yun’s iteration maintains and with , peeling one multiplicity per round. (In characteristic , means is a -th power: take the -th root of the coefficients.)
EDF (Cantor–Zassenhaus). By CRT, . For uniform random the components are independent uniform elements of , and maps onto , each with probability (half of the cyclic group are squares). The gcd with is the product of the factors whose component is , a non-trivial split unless all components agree, which has probability at most for . The exponent is computed as because . Roots: is the product of the distinct linear factors of ( above), which EDF splits; this generalises Tonelli–Shanks (). Rabin: of degree is irreducible iff it divides (all factors have degree dividing ) and has no factor of degree dividing for a prime (every proper divisor of divides some ). All functions were checked: factor products reproduce , every factor passes Rabin and has the degree reported, roots match polynomials built from known roots, and Rabin agrees with brute-force trial division over -sized analogues.
Berlekamp
For square-free , CRT gives . The map is -linear and its kernel, the Berlekamp subalgebra, consists exactly of the elements that are constant in every CRT component, so it has dimension . Build the matrix whose row is ; the kernel of gives and a basis . For a non-constant in the kernel and , is the product of the whose component of is : a non-trivial factor for some . Building is , the kernel , splitting : linear in , fine for small primes (coding theory over ), hopeless for large ones, where Cantor–Zassenhaus-style random splitting replaces it. Berlekamp designed it for error-correcting codes, like his Berlekamp–Massey algorithm (Linear Recurrences).
using PolyP = vector<ll>; // coefficients mod a small prime p, low degree first
PolyP trimP(PolyP a) { while (!a.empty() && a.back() == 0) a.pop_back(); return a; }
PolyP modP(PolyP a, const PolyP& b, ll p) { // b monic
a = trimP(a); int m = sz(b) - 1;
for (int i = sz(a) - 1; i >= m; i--) { ll c = a[i]; if (!c) continue; for (int j = 0; j <= m; j++) a[i - m + j] = ((a[i - m + j] - c * b[j]) % p + p) % p; }
if (sz(a) > m) a.resize(m); return trimP(a);
}
ll invP(ll a, ll p) { ll r = 1, e = p - 2; a %= p; for (; e; e >>= 1, a = a * a % p) if (e & 1) r = r * a % p; return r; }
PolyP monicP(PolyP a, ll p) { a = trimP(a); if (a.empty()) return a; ll iv = invP(a.back(), p); for (auto& x : a) x = x * iv % p; return a; }
PolyP gcdP(PolyP a, PolyP b, ll p) { a = trimP(a), b = trimP(b); while (!b.empty()) { b = monicP(b, p); a = modP(a, b, p); swap(a, b); } return monicP(a, p); }
PolyP mulP(const PolyP& a, const PolyP& b, ll p) { if (a.empty() || b.empty()) return {}; PolyP c(sz(a) + sz(b) - 1, 0); for (int i = 0; i < sz(a); i++) for (int j = 0; j < sz(b); j++) c[i + j] = (c[i + j] + a[i] * b[j]) % p; return c; }
PolyP divP(PolyP a, const PolyP& b, ll p) { // exact quotient, b monic
a = trimP(a); int m = sz(b) - 1; if (sz(a) - 1 < m) return {};
PolyP q(sz(a) - m, 0);
for (int i = sz(a) - 1; i >= m; i--) { ll c = a[i]; q[i - m] = c; if (!c) continue; for (int j = 0; j <= m; j++) a[i - m + j] = ((a[i - m + j] - c * b[j]) % p + p) % p; }
return q;
}
vector<PolyP> berlekampFactor(PolyP f, ll p) { // f monic square-free over F_p (small p); irreducible factors
int n = sz(f) - 1; if (n <= 1) return {f};
vector<vector<ll>> M(n, vector<ll>(n, 0)); // row i: x^{p i} mod f, minus identity
PolyP xp = {1}, base = modP([&] { PolyP t(p + 1, 0); t[p] = 1; return t; }(), f, p);
for (int i = 0; i < n; i++) { for (int j = 0; j < sz(xp); j++) M[i][j] = xp[j]; M[i][i] = (M[i][i] + p - 1) % p; xp = modP(mulP(xp, base, p), f, p); }
// kernel of M^T: vectors v with sum_i v_i (row_i) = 0, i.e. g = sum v_i x^i with g^p = g mod f
vector<vector<ll>> A(n, vector<ll>(n)); for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) A[j][i] = M[i][j];
vector<int> where(n, -1); int row = 0;
for (int col = 0; col < n && row < n; col++) {
int piv = -1; for (int r = row; r < n; r++) if (A[r][col]) { piv = r; break; }
if (piv < 0) continue;
swap(A[piv], A[row]); ll iv = invP(A[row][col], p); for (auto& x : A[row]) x = x * iv % p;
for (int r = 0; r < n; r++) if (r != row && A[r][col]) { ll c = A[r][col]; for (int j = 0; j < n; j++) A[r][j] = ((A[r][j] - c * A[row][j]) % p + p) % p; }
where[col] = row++;
}
vector<PolyP> basis;
for (int fcol = 0; fcol < n; fcol++) if (where[fcol] == -1) {
PolyP v(n, 0); v[fcol] = 1; for (int j = 0; j < n; j++) if (where[j] != -1) v[j] = (p - A[where[j]][fcol]) % p;
basis.push_back(trimP(v));
}
int k = sz(basis); vector<PolyP> fs = {f};
for (auto& g : basis) {
if (sz(fs) == k) break;
if (sz(g) <= 1) continue; // constants split nothing
vector<PolyP> nxt;
for (auto& h : fs) {
if (sz(h) <= 2) { nxt.push_back(h); continue; }
for (ll c = 0; c < p && sz(h) > 1; c++) {
PolyP gc = g; gc[0] = (gc[0] - c % p + p) % p; PolyP d = gcdP(h, gc, p);
if (sz(d) > 1 && sz(d) < sz(h)) { nxt.push_back(d); h = divP(h, d, p); }
}
if (sz(h) > 1) nxt.push_back(h);
}
fs = nxt;
}
return fs;
}
ll countMonicIrreducible(int n, ll q) { // (1/n) sum_{d | n} mu(d) q^{n/d}, exact for small values
ll s = 0;
for (int d = 1; d <= n; d++) if (n % d == 0) {
int mu = 1, x = d; for (int r = 2; r <= x; r++) if (x % r == 0) { x /= r; if (x % r == 0) { mu = 0; break; } mu = -mu; }
ll pw = 1; for (int i = 0; i < n / d; i++) pw *= q; s += mu * pw;
}
return s / n;
}Proof: the Berlekamp subalgebra, the splitting step and the irreducible count
Kernel. In , holds iff every component satisfies , i.e. lies in the prime-power subfield (the roots of ). So the kernel is , of dimension . Since is linear over (Frobenius), it is the matrix in the basis , and the kernel of (acting on coefficient row vectors) is computed by elimination on the transpose. Splitting. For in the kernel with components , iff , so . Two factors are separated by some basis vector (otherwise every kernel element would have , contradicting ), so processing all basis vectors with all separates every pair, and the loop stops when factors are found. Counting irreducibles: comparing degrees in gives , and Möbius inversion gives . Checked over against brute-force factorisation by trial division and against enumerated irreducible counts.
Cyclotomic polynomials
is the factorisation over ; (degree ) has the primitive -th roots of unity as roots, integer coefficients, and is irreducible over . . Coefficients are in for ( has a ) but grow for with many odd prime factors. ; if , else 1 ().
vector<ll> cyclotomic(int n) { // coefficients of Phi_n, O(n d(n))
vector<int> mu(n + 1, 1); vector<char> comp(n + 1, 0);
for (int p = 2; p <= n; p++) if (!comp[p]) for (int j = p; j <= n; j += p) { if (j > p) comp[j] = 1; mu[j] = (j / p) % p == 0 ? 0 : -mu[j]; }
vector<ll> poly = {1};
auto mulXd = [&](int d) { vector<ll> r(sz(poly) + d, 0); for (int i = 0; i < sz(poly); i++) r[i + d] += poly[i], r[i] -= poly[i]; poly = r; }; // * (x^d - 1)
auto divXd = [&](int d) { int m = sz(poly); vector<ll> r(m - d, 0); for (int i = m - 1; i >= d; i--) { r[i - d] = poly[i]; poly[i - d] += poly[i]; } poly = r; }; // / (x^d - 1), exact
for (int d = 1; d <= n; d++) if (n % d == 0 && mu[n / d] == 1) mulXd(d);
for (int d = 1; d <= n; d++) if (n % d == 0 && mu[n / d] == -1) divXd(d);
return poly;
}
vector<pair<int, vector<ll>>> factorXnMinus1(int n) { vector<pair<int, vector<ll>>> r; for (int d = 1; d <= n; d++) if (n % d == 0) r.push_back({d, cyclotomic(d)}); return r; }Proof: the cyclotomic product formula
Every -th root of unity is a primitive -th root for exactly one , so . Taking logarithms (or working in the multiplicative group of non-zero rational functions) and applying Möbius inversion gives . Multiplying the factors with first and then dividing by those with keeps everything a polynomial (each partial quotient is a product of cyclotomic polynomials) and the divisions by the monic are exact with integer coefficients (the division loop is synthetic division by ). Irreducibility over is Gauss’s / Dedekind’s theorem (quoted). Verified: for , , and the in .
Related notes
- Modular Square Roots (Tonelli–Shanks is )
- GCD and Resultant
- Lyndon Words (the same counting formula)