The Euclidean algorithm for polynomials over a field: gcd, Bézout coefficients and inverses modulo a polynomial in by half-gcd, and the resultant (zero iff , share a root) with its uses: eliminating a variable, discriminants, circulant determinants. Uses the core of NTT and Power Series (polyMul, polyDivMod).

TaskMethodCost
, Bézout schoolbook Euclid
same, large degreeshalf-gcd (Knuth–Schönhage)
Bézout coefficient when (Library Checker “Inv of Polynomials”)
Euclid with leading-coefficient bookkeeping (half-gcd version )
discriminant resultant
squarefree part gcdas above
eliminate from is a polynomial in vanishing at the common solutionsinterpolate over

Half-gcd. One Euclid step maps , a matrix . The quotients of the first steps depend only on the top coefficients: from the top halves of and one computes recursively the product of all steps that bring the degree below , applies it, does one step, and recurses on the top half again; the full gcd repeats half-gcd.

struct PolyEuclid {                                         // 2x2 polynomial matrices for Euclid steps
    using Pair = array<Poly, 2>; using M = array<Pair, 2>;
    static Poly strip(Poly a) { while (!a.empty() && a.back() == mint(0)) a.pop_back(); return a; }
    static Poly mul(const Poly& a, const Poly& b) { return a.empty() || b.empty() ? Poly() : polyMul(a, b); }
    static Poly add(Poly a, const Poly& b, bool neg = false) { if (sz(a) < sz(b)) a.resize(sz(b)); for (int i = 0; i < sz(b); i++) a[i] += neg ? -b[i] : b[i]; return strip(a); }
    static M identity() { return {Pair{Poly{1}, Poly{}}, Pair{Poly{}, Poly{1}}}; }
    static Pair apply(const M& m, const Pair& v) { Pair r; for (int i = 0; i < 2; i++) r[i] = add(mul(m[i][0], v[0]), mul(m[i][1], v[1])); return r; }
    static M compose(const M& a, const M& b) { M r; for (int i = 0; i < 2; i++) for (int k = 0; k < 2; k++) r[i][k] = add(mul(a[i][0], b[0][k]), mul(a[i][1], b[1][k])); return r; }
    static Pair cut(Pair a, int k) { for (auto& p : a) p = sz(p) <= k ? Poly() : Poly(p.begin() + k, p.end()); return a; }   // divide by x^k
    static void step(M& m, Pair& a) {                        // (a0, a1) -> (a1, a0 - q a1)
        Poly q = strip(polyDivMod(a[0], a[1]).first);
        Poly r = add(a[0], mul(q, a[1]), true);
        a = {a[1], r};
        m = {m[1], Pair{add(m[0][0], mul(q, m[1][0]), true), add(m[0][1], mul(q, m[1][1]), true)}};
    }
    static M halfGcd(Pair a) {                              // deg a0 > deg a1 >= 0
        int m = sz(a[0]) / 2;
        if (sz(a[1]) <= m) return identity();
        M R = halfGcd(cut(a, m)); a = apply(R, a);
        if (sz(a[1]) <= m) return R;
        step(R, a);
        if (sz(a[1]) <= m) return R;
        int l = sz(a[0]) - 1, k = 2 * m - l;
        return compose(halfGcd(cut(a, k)), R);
    }
    static M euclid(Pair a) {                               // all steps until a1 = 0
        M m0 = halfGcd(a); a = apply(m0, a);
        if (a[1].empty()) return m0;
        step(m0, a);
        if (a[1].empty()) return m0;
        return compose(euclid(a), m0);
    }
    static M run(const Poly& a, const Poly& b) {            // M (a, b) = (gcd, 0) up to a constant
        M res = identity(); Pair v = {strip(a), strip(b)};
        if (v[1].empty()) return res;
        step(res, v);
        if (!v[1].empty()) res = compose(euclid(v), res);
        return res;
    }
};
Poly polyGcd(Poly a, Poly b) {                              // monic gcd, {} if both zero
    a = PolyEuclid::strip(a), b = PolyEuclid::strip(b);
    if (b.empty()) swap(a, b);
    if (b.empty()) return {};
    auto m = PolyEuclid::run(a, b);
    Poly g = PolyEuclid::add(PolyEuclid::mul(m[0][0], a), PolyEuclid::mul(m[0][1], b));
    mint inv = g.back().inv(); for (auto& x : g) x *= inv;
    return g;
}
Poly polyInverseMod(Poly a, Poly b) {                       // x with a x = 1 mod b, deg x < deg b; {} if gcd != 1
    a = PolyEuclid::strip(a), b = PolyEuclid::strip(b);
    if (b.empty() || a.empty()) return {};
    auto m = PolyEuclid::run(a, b);
    Poly g = PolyEuclid::add(PolyEuclid::mul(m[0][0], a), PolyEuclid::mul(m[0][1], b));
    if (sz(g) != 1) return {};
    Poly x = m[0][0]; mint inv = g[0].inv(); for (auto& c : x) c *= inv;
    if (sz(x) >= sz(b)) x = polyDivMod(x, b).second;
    return x;
}
 
mint resultant(Poly a, Poly b) {                           // O(deg a deg b), any lengths
    a = PolyEuclid::strip(a), b = PolyEuclid::strip(b);
    if (a.empty() || b.empty()) return 0;
    mint res = 1;
    while (true) {
        if (sz(b) == 1) { for (int i = 0; i + 1 < sz(a); i++) res *= b[0]; return res; }   // Res(A, c) = c^deg A
        if (sz(a) == 1) { for (int i = 0; i + 1 < sz(b); i++) res *= a[0]; return res; }   // Res(c, B) = c^deg B
        Poly c = polyDivMod(a, b).second;
        if (c.empty()) return 0;
        int da = sz(a) - 1, db = sz(b) - 1, dc = sz(c) - 1;
        if ((ll)da * db % 2) res = -res;
        for (int i = 0; i < da - dc; i++) res *= b.back();
        a = b, b = c;
    }
}
mint discriminant(const Poly& f) {                          // (-1)^{n(n-1)/2} Res(f, f') / lc(f), n = deg f >= 1
    Poly g = PolyEuclid::strip(f); int n = sz(g) - 1;
    mint r = resultant(g, derivative(g)) / g.back();
    return ((ll)n * (n - 1) / 2) % 2 ? -r : r;
}