// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // Polynomial_factorization.hpp // 整数多項式の厳密な因数分解 // // アルゴリズム: // 1. content 抽出 (全係数の GCD) // 2. 有理根定理による 1 次因子分離 // 3. Yun の無平方分解 (gcd(f, f') による重複因子分離) // 4. Kronecker 法による高次因子の試行 // // 使用法: // #include // #include // #include // 結果の型 #pragma once #include #include #include #include #include #include #include #include #include namespace sangi { // ================================================================ // 内部ヘルパー // ================================================================ namespace detail { // 整数の約数を列挙 (正の約数のみ、ソート済み) inline std::vector divisors(int n) { if (n < 0) n = -n; if (n == 0) return {}; std::vector result; for (int i = 1; i * i <= n; ++i) { if (n % i == 0) { result.push_back(i); if (i != n / i) result.push_back(n / i); } } std::sort(result.begin(), result.end()); return result; } // 整数の約数を列挙 (int64_t 版) inline std::vector divisors(int64_t n) { if (n < 0) n = -n; if (n == 0) return {}; std::vector result; for (int64_t i = 1; i * i <= n; ++i) { if (n % i == 0) { result.push_back(i); if (i != n / i) result.push_back(n / i); } } std::sort(result.begin(), result.end()); return result; } // Lagrange 補間で多項式の係数を復元する // xs, ys: 評価点と値 (n+1 個 → 最大 n 次多項式) template Polynomial lagrangeInterpolationPoly( const std::vector& xs, const std::vector& ys) { int n = static_cast(xs.size()); Polynomial result(T(0)); Polynomial one(T(1)); for (int i = 0; i < n; ++i) { if (ys[i] == T(0)) continue; // L_i(x) = Π_{j≠i} (x - x_j) / (x_i - x_j) Polynomial basis = one; T denom = T(1); for (int j = 0; j < n; ++j) { if (j == i) continue; // (x - x_j) basis = basis * Polynomial({T(0) - xs[j], T(1)}); denom = denom * (xs[i] - xs[j]); } // ys[i] / denom * basis result = result + basis * (ys[i] / denom); } return result; } // 多項式が整数係数かチェック (T が整数型なら常に true) template bool hasIntegerCoeffs(const Polynomial& p) { if constexpr (std::is_integral_v) { return true; } else { // 浮動小数点の場合: 各係数が整数に十分近いかチェック for (int i = 0; i <= p.degree(); ++i) { double v = static_cast(p[i]); if (std::abs(v - std::round(v)) > 1e-9) return false; } return true; } } // 多項式を整数化して返す (有理数 Lagrange 結果 → 整数係数チェック) template bool tryRoundToInt(const Polynomial& src, Polynomial& dst) { std::vector coeffs(src.degree() + 1); for (int i = 0; i <= src.degree(); ++i) { double v = src[i]; double r = std::round(v); if (std::abs(v - r) > 1e-6) return false; coeffs[i] = static_cast(static_cast(r)); } dst = Polynomial(std::move(coeffs)); return true; } } // namespace detail // ================================================================ // 整数多項式の GCD (擬剰余ベース) // ================================================================ /// 整数多項式の擬剰余 (pseudo-remainder) /// prem(f, g) = lc(g)^(deg(f)-deg(g)+1) * f mod g /// 結果は必ず整数係数を保つ template [[nodiscard]] Polynomial pseudoRemainder( const Polynomial& f, const Polynomial& g) { static_assert(std::is_integral_v); if (g.isZero()) return f; if (f.degree() < g.degree()) return f; int delta = f.degree() - g.degree() + 1; T lc_g = g.leadingCoefficient(); // lc(g)^delta * f T scale = T(1); for (int i = 0; i < delta; ++i) scale *= lc_g; Polynomial r = f * scale; auto dr = r.divmod(g); return dr.remainder; } /// 整数多項式の GCD (subresultant PRS) /// 各ステップで擬剰余を取り、primitivePart で係数を縮小する template [[nodiscard]] Polynomial intPolyGcd(Polynomial a, Polynomial b) { static_assert(std::is_integral_v); // 零チェック if (a.isZero()) return b; if (b.isZero()) return a; // 両方 primitive にする a = primitivePart(a); b = primitivePart(b); // deg(a) >= deg(b) を保証 if (a.degree() < b.degree()) std::swap(a, b); while (!b.isZero()) { Polynomial r = pseudoRemainder(a, b); if (r.isZero()) break; r = primitivePart(r); a = std::move(b); b = std::move(r); } // b が 0 なら a が GCD、そうでなければ b が GCD Polynomial& result = b.isZero() ? a : b; // 主係数を正にする if (result.leadingCoefficient() < T(0)) result = -result; return primitivePart(result); } // ================================================================ // 無平方分解 (Yun's algorithm) // ================================================================ /// Yun のアルゴリズムで多項式 f を無平方因子に分解する /// 結果: f = c * Π f_i^i (f_i は無平方で互いに素) /// 返値は FactoredInteger> で、各因子と冪を保持 /// /// T は整数型 (int, int64_t 等) template [[nodiscard]] FactoredInteger> squareFreeFactorization( const Polynomial& f) { static_assert(std::is_integral_v, "squareFreeFactorization requires integral coefficient type"); FactoredInteger> result; if (f.isZero() || f.degree() <= 0) { if (!f.isZero()) result.addFactor(f, 1); return result; } Polynomial fd = f.derivative(); if (fd.isZero()) { // f' = 0 → f は定数の可能性 (or char p で全冪が p の倍数) result.addFactor(f, 1); return result; } // 擬剰余ベースの整数多項式 GCD を使用 Polynomial pp = primitivePart(f); Polynomial ppd = pp.derivative(); Polynomial g = intPolyGcd(pp, ppd); Polynomial v = pp / g; Polynomial w = ppd / g; for (int n = 1; !v.isZero() && v.degree() > 0; ++n) { Polynomial vd = v.derivative(); Polynomial diff = w - vd; Polynomial h = intPolyGcd(v, diff); if (h.degree() > 0) { // 主係数を正にする if (h.leadingCoefficient() < T(0)) h = -h; result.addFactor(h, n); } v = v / h; if (diff.isZero()) break; w = diff / h; } // 残りの v が定数でなければ追加 if (!v.isZero() && v.degree() > 0) { if (v.leadingCoefficient() < T(0)) v = -v; result.addFactor(v, 1); } return result; } // ================================================================ // 有理根定理による 1 次因子分離 // ================================================================ namespace detail { /// 有理根定理: f(b/a) = 0 となる有理数 b/a を探し、 /// (ax - b) の因子を全て分離する /// f は primitive (content = 1) であること template void extractLinearFactors( Polynomial& f, FactoredInteger>& factors, int multiplicity = 1) { static_assert(std::is_integral_v); while (f.degree() >= 1) { T f0 = f[0]; T fn = f.leadingCoefficient(); if (f0 < T(0)) f0 = -f0; if (fn < T(0)) fn = -fn; auto divs_f0 = divisors(static_cast(f0)); auto divs_fn = divisors(static_cast(fn)); // f0=0 なら x=0 が根 → (x) で割り切れる if (f[0] == T(0)) { divs_f0 = {0}; } bool found = false; // 候補: x = ±b/a (b | f0, a | fn) // 整数多項式のまま判定: f(b/a) = 0 ⟺ f を (ax - b) で割った余りが 0 for (auto a : divs_fn) { if (a == 0) continue; for (auto b : divs_f0) { for (int sign : {1, -1}) { T bv = static_cast(b * sign); T av = static_cast(a); // 候補因子: (av*x - bv) = av * x + (-bv) Polynomial candidate({T(0) - bv, av}); auto dr = f.divmod(candidate); if (dr.remainder.isZero()) { if (candidate.leadingCoefficient() < T(0)) candidate = -candidate; factors.addFactor(candidate, multiplicity); f = std::move(dr.quotient); found = true; goto next_round; } } } } next_round: if (!found) break; } } } // namespace detail // ================================================================ // Kronecker 法による因数分解 // ================================================================ namespace detail { /// Kronecker のアルゴリズム: /// deg(f) = n の因子は最大 n/2 次 → n/2+1 個の点で評価し、 /// 値の約数の全組み合わせを Lagrange 補間して因子を試す template void kroneckerFactor( Polynomial& f, FactoredInteger>& factors, int multiplicity = 1) { static_assert(std::is_integral_v); while (f.degree() >= 2) { int deg = f.degree(); int half = deg / 2; int npts = half + 1; // 評価点を選択 (0 中心) std::vector xs(npts); int offset = half / 2; for (int i = 0; i < npts; ++i) xs[i] = i - offset; // 各点での値を計算し、約数を列挙 std::vector> allDivs(npts); for (int i = 0; i < npts; ++i) { T val = f(static_cast(xs[i])); int64_t v = static_cast(val); auto d = divisors(v); std::vector fullDivs; fullDivs.reserve(d.size() * 2); for (auto dv : d) { fullDivs.push_back(dv); fullDivs.push_back(-dv); } if (v == 0) { fullDivs.clear(); for (int64_t k = -10; k <= 10; ++k) fullDivs.push_back(k); } allDivs[i] = std::move(fullDivs); } size_t totalCombs = 1; for (int i = 0; i < npts; ++i) { if (allDivs[i].empty()) { totalCombs = 0; break; } totalCombs *= allDivs[i].size(); if (totalCombs > 100000) { totalCombs = 100000; break; } } bool found = false; for (size_t idx = 0; idx < totalCombs; ++idx) { std::vector xsd(npts), ysd(npts); size_t rem = idx; for (int i = 0; i < npts; ++i) { size_t j = rem % allDivs[i].size(); rem /= allDivs[i].size(); xsd[i] = static_cast(xs[i]); ysd[i] = static_cast(allDivs[i][j]); } Polynomial cand_d = lagrangeInterpolationPoly(xsd, ysd); Polynomial candidate; if (!tryRoundToInt(cand_d, candidate)) continue; if (candidate.degree() <= 0) continue; if (candidate.degree() > half) continue; T candLC = candidate.leadingCoefficient(); if (candLC < T(0)) { candidate = -candidate; candLC = -candLC; } T fLC = f.leadingCoefficient(); if (fLC < T(0)) fLC = -fLC; if (candLC == T(0) || fLC % candLC != T(0)) continue; auto dr = f.divmod(candidate); if (dr.remainder.isZero()) { factors.addFactor(candidate, multiplicity); f = std::move(dr.quotient); found = true; break; } } if (!found) break; } } } // namespace detail // ================================================================ // 因数分解 (メインエントリ) // ================================================================ /// 整数多項式 f を既約因子に分解する /// 結果: FactoredInteger> で f = content * Π factor_i ^ exp_i /// /// アルゴリズム: /// 1. content (全係数の GCD) を定数因子として抽出 /// 2. 有理根定理で 1 次因子を分離 /// 3. Yun の無平方分解で重複因子を分離 /// 4. 各無平方因子に対して Kronecker 法で高次因子を分離 #ifndef SANGI_HAS_FACTORIZE_PRO template [[nodiscard]] FactoredInteger> factorize(const Polynomial& f) { static_assert(std::is_integral_v, "factorize requires integral coefficient type (int or int64_t)"); FactoredInteger> result; if (f.isZero()) return result; // 1. content 抽出 T c = content(f); Polynomial prim = f; if (c != T(1) && c != T(0)) { prim = primitivePart(f); if (c != T(1)) result.addFactor(Polynomial(c), 1); } // 主係数を正にする if (!prim.isZero() && prim.leadingCoefficient() < T(0)) { prim = -prim; if (result.size() > 0 && result[0].degree() == 0) result[0] = Polynomial(T(0) - result[0][0]); else result.addFactor(Polynomial(T(-1)), 1); } if (prim.degree() <= 0) { if (!prim.isZero() && !(prim[0] == T(1))) result.addFactor(prim, 1); return result; } if (prim.degree() == 1) { result.addFactor(prim, 1); return result; } // 2. 有理根定理で 1 次因子を先に分離 detail::extractLinearFactors(prim, result); if (prim.degree() <= 0) return result; if (prim.degree() == 1) { if (prim.leadingCoefficient() < T(0)) prim = -prim; result.addFactor(prim, 1); return result; } // 3. 無平方分解 auto sqfree = squareFreeFactorization(prim); if (sqfree.empty()) { result.addFactor(prim, 1); return result; } // 4. 各無平方因子を Kronecker 法で分解 for (size_t i = 0; i < sqfree.size(); ++i) { Polynomial fi = sqfree[i]; int mult = sqfree.exponent(i); if (fi.degree() <= 0) continue; detail::extractLinearFactors(fi, result, mult); if (fi.degree() <= 0) continue; if (fi.degree() == 1) { if (fi.leadingCoefficient() < T(0)) fi = -fi; result.addFactor(fi, mult); continue; } detail::kroneckerFactor(fi, result, mult); if (fi.degree() > 0) { if (fi.leadingCoefficient() < T(0)) fi = -fi; result.addFactor(fi, mult); } } return result; } #else // SANGI_HAS_FACTORIZE_PRO template [[nodiscard]] FactoredInteger> factorize(const Polynomial& f); #endif // SANGI_HAS_FACTORIZE_PRO #ifdef SANGI_HAS_FACTORIZE_PRO extern template FactoredInteger> factorize(const Polynomial&); extern template FactoredInteger> factorize(const Polynomial&); class Int; template<> [[nodiscard]] FactoredInteger> factorize(const Polynomial& f); #endif // ======================================================================== // CAS-4: Resultant / Discriminant (Sylvester 行列ベース) // ======================================================================== /// Sylvester 行列を構築 /// f(x) の次数 m, g(x) の次数 n → (m+n)×(m+n) 行列 template Matrix sylvesterMatrix(const Polynomial& f, const Polynomial& g) { int m = f.degree(), n = g.degree(); int sz = m + n; Matrix S(sz, sz, T(0)); // f の係数を n 行分シフトして配置 for (int i = 0; i < n; ++i) for (int j = 0; j <= m; ++j) S(i, i + j) = f[m - j]; // 降順 // g の係数を m 行分シフトして配置 for (int i = 0; i < m; ++i) for (int j = 0; j <= n; ++j) S(n + i, i + j) = g[n - j]; // 降順 return S; } /// 終結式 Res(f, g) = det(Sylvester(f, g)) template [[nodiscard]] T resultant(const Polynomial& f, const Polynomial& g) { if (f.degree() == 0 || g.degree() == 0) return T(0); auto S = sylvesterMatrix(f, g); // LU 分解で行列式を計算 (簡易: ガウス消去) int n = static_cast(S.rows()); T det = T(1); for (int i = 0; i < n; ++i) { // ピボット選択 int pivot = -1; for (int k = i; k < n; ++k) if (S(k, i) != T(0)) { pivot = k; break; } if (pivot < 0) return T(0); if (pivot != i) { for (int j = 0; j < n; ++j) std::swap(S(i, j), S(pivot, j)); det = -det; } det *= S(i, i); T inv = S(i, i); for (int k = i + 1; k < n; ++k) { T factor = S(k, i); if (factor == T(0)) continue; for (int j = i; j < n; ++j) S(k, j) = S(k, j) * inv - S(i, j) * factor; // 整数除算でスケール調整 if (i > 0) { for (int j = i; j < n; ++j) S(k, j) /= det / S(i, i); // 仮 (浮動小数点の場合) } } } return det; } /// 判別式 Disc(f) = (-1)^(n(n-1)/2) * Res(f, f') / a_n /// a_n = 最高次係数 template [[nodiscard]] T discriminant(const Polynomial& f) { int n = f.degree(); if (n < 1) return T(0); auto fp = f.derivative(); T res = resultant(f, fp); T an = f[n]; T sign = ((n * (n - 1) / 2) % 2 == 0) ? T(1) : T(-1); if (an != T(0)) return sign * res / an; return sign * res; } // ======================================================================== // CAS-10: 整数分割 // ======================================================================== /// n の全分割を列挙 (降順パーティション) [[nodiscard]] inline std::vector> integerPartitions(int n) { std::vector> result; if (n <= 0) { result.push_back({}); return result; } std::function&)> gen; gen = [&](int remaining, int maxPart, std::vector& current) { if (remaining == 0) { result.push_back(current); return; } for (int k = std::min(remaining, maxPart); k >= 1; --k) { current.push_back(k); gen(remaining - k, k, current); current.pop_back(); } }; std::vector buf; gen(n, n, buf); return result; } /// 分割数 p(n) (動的計画法) [[nodiscard]] inline uint64_t partitionCount(int n) { if (n < 0) return 0; std::vector dp(n + 1, 0); dp[0] = 1; for (int k = 1; k <= n; ++k) for (int j = k; j <= n; ++j) dp[j] += dp[j - k]; return dp[n]; } } // namespace sangi