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 ).

AlgorithmTypeTimeBest for
Berlekampdeterministicsmall (e.g. ); gives the number of factors first
Cantor–ZassenhausLas Vegaslarge : the standard
von zur Gathen–Shouprandomised (baby-step giant-step DDF)asymptotically faster DDF
Kaltofen–Shouprandomisedtheoretical
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 / countMethod
irreducible over (Rabin) and for every prime
irreducible via Berlekamp
over : Eisensteina 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;
}

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;
}

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; }