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).
| Task | Method | Cost |
|---|---|---|
| , Bézout | schoolbook Euclid | |
| same, large degrees | half-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 | gcd | as above |
| eliminate from | is a polynomial in vanishing at the common solutions | interpolate 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;
}Proof: half-gcd, Bézout and the resultant recursion
Euclid as matrices. Each step multiplies the pair by (invertible, determinant ), so after all steps with a gcd and the first row of the Bézout coefficients;
stepupdates by left multiplication. Half-gcd. Write with . Key lemma (von zur Gathen–Gerhard, Modern Computer Algebra, §11.1): the Euclidean quotients of , as long as the remainders keep degree at least , coincide with the corresponding quotients of , because a quotient with is determined by the top coefficients of dividend and divisor, and the low parts only influence coefficients below times the growth of the previous quotients. Hence the matrix returned for consists of genuine Euclid steps of ; after applying it and one explicit step, the second recursive call on the top part (cut at ) again performs only genuine steps, and the procedure stops exactly when the remainder degree drops to at most (thesz <= mtests). Each call makes two recursive calls on inputs of half the size plus multiplication work, so .euclidalternates half-gcd and single steps until the remainder is 0 (the degree halves each round, so the total is still ). Inverse: if constant then , reduced mod .Resultant. From the product formula (roots in a splitting field): , and for , ; a constant gives ; a zero remainder with means a common factor, so the resultant is 0. The product formula equals the Sylvester determinant (both are the unique polynomial in the coefficients that vanishes exactly on common roots, with the same normalisation; or directly by the standard row-reduction argument). Discriminant: and , so with . Checked: half-gcd against schoolbook Euclid for random and structured (common factor) inputs; resultant against the Sylvester determinant; discriminant of quadratics .
Related notes
- Structured Matrices (circulant determinant via resultant)
- Polynomial Factorization (gcd with )
- GCD and Extended Euclid (the integer analogue)