結果

問題 No.3669 误差绝不允许
コンテスト
ユーザー harurun
提出日時 2026-08-06 17:16:35
言語 C++23(gcc16)
(gcc 16.1.0 + boost 1.92.0)
コンパイル:
g++-16 -O2 -lm -std=c++23 -Wuninitialized -DONLINE_JUDGE -o a.out _filename_
実行:
./a.out
結果
TLE  
実行時間 -
コード長 63,241 bytes
記録
記録タグの例:
初AC ショートコード 純ショートコード 純主流ショートコード 最速実行時間
コンパイル時間 4,994 ms
コンパイル使用メモリ 401,200 KB
実行使用メモリ 8,564 KB
最終ジャッジ日時 2026-09-04 22:08:10
合計ジャッジ時間 10,750 ms
ジャッジサーバーID
(参考情報)
judge3_0 / judge1_0
このコードへのチャレンジ
(要ログイン)
ファイルパターン 結果
sample AC * 2
other AC * 10 TLE * 1 -- * 19
権限があれば一括ダウンロードができます

ソースコード

diff #
raw source code

#include <bits/stdc++.h>
using namespace std;

// #line 1 "src/algorithm/math/integer/big_integer.hpp"



// #include <compare>
// #include <concepts>
// #include <cstddef>
// #include <istream>
// #include <ostream>
// #include <stdexcept>
// #include <string>
// #include <string_view>
// #include <utility>

// #line 1 "src/algorithm/math/integer/exact_integer.hpp"



// #include <algorithm>
// #include <bit>
// #line 9 "src/algorithm/math/integer/exact_integer.hpp"
// #include <cstdint>
// #include <limits>
// #line 14 "src/algorithm/math/integer/exact_integer.hpp"
// #include <type_traits>
// #line 16 "src/algorithm/math/integer/exact_integer.hpp"
// #include <vector>

namespace exact_integer_detail{

template<class Integer>
inline constexpr bool native_integer =
    std::is_integral_v<std::remove_cv_t<Integer>>
#if defined(__SIZEOF_INT128__)
    || std::same_as<std::remove_cv_t<Integer>, __int128_t>
    || std::same_as<std::remove_cv_t<Integer>, __uint128_t>
#endif
    ;

template<class Integer>
concept NativeInteger = native_integer<Integer>;

template<class Integer>
struct MakeUnsigned{
    using type = std::make_unsigned_t<Integer>;
};

#if defined(__SIZEOF_INT128__)
template<>
struct MakeUnsigned<__int128_t>{
    using type = __uint128_t;
};

template<>
struct MakeUnsigned<__uint128_t>{
    using type = __uint128_t;
};
#endif

template<class Integer>
using MakeUnsignedT = typename MakeUnsigned<std::remove_cv_t<Integer>>::type;

}  // namespace exact_integer_detail

class ExactInteger{
    static constexpr std::uint64_t limb_base = std::uint64_t{1} << 32;
    static constexpr std::uint64_t decimal_base = 1'000'000'000;

    using DecimalMagnitude = std::vector<std::uint32_t>;

    std::vector<std::uint32_t> limbs_;
    bool negative_ = false;

    void normalize(){
        while(!limbs_.empty() && limbs_.back() == 0) limbs_.pop_back();
        if(limbs_.empty()) negative_ = false;
    }

    template<exact_integer_detail::NativeInteger Integer>
    void assign_integral(Integer value){
        limbs_.clear();
        negative_ = false;
        using Value = std::remove_cv_t<Integer>;
        if constexpr(std::same_as<Value, bool>){
            if(value) limbs_.push_back(1);
            return;
        }else{
            using Unsigned = exact_integer_detail::MakeUnsignedT<Value>;
            Unsigned magnitude = static_cast<Unsigned>(value);
            if constexpr(std::numeric_limits<Value>::is_signed){
                if(value < 0){
                    negative_ = true;
                    magnitude = Unsigned{0} - magnitude;
                }
            }
            while(magnitude != 0){
                limbs_.push_back(static_cast<std::uint32_t>(magnitude));
                if constexpr(std::numeric_limits<Unsigned>::digits > 32){
                    magnitude >>= 32;
                }else{
                    magnitude = 0;
                }
            }
        }
    }

    static int compare_magnitude(
        const ExactInteger& left,
        const ExactInteger& right
    ){
        if(left.limbs_.size() != right.limbs_.size()){
            return left.limbs_.size() < right.limbs_.size() ? -1 : 1;
        }
        for(std::size_t index = left.limbs_.size(); index-- > 0;){
            if(left.limbs_[index] != right.limbs_[index]){
                return left.limbs_[index] < right.limbs_[index] ? -1 : 1;
            }
        }
        return 0;
    }

    static std::vector<std::uint32_t> add_magnitudes(
        const std::vector<std::uint32_t>& left,
        const std::vector<std::uint32_t>& right
    ){
        const std::size_t size = std::max(left.size(), right.size());
        std::vector<std::uint32_t> result(size + 1, 0);
        std::uint64_t carry = 0;
        for(std::size_t index = 0; index < size; ++index){
            const std::uint64_t sum = carry
                + (index < left.size() ? left[index] : 0)
                + (index < right.size() ? right[index] : 0);
            result[index] = static_cast<std::uint32_t>(sum);
            carry = sum >> 32;
        }
        result[size] = static_cast<std::uint32_t>(carry);
        return result;
    }

    static std::vector<std::uint32_t> subtract_magnitudes(
        const std::vector<std::uint32_t>& larger,
        const std::vector<std::uint32_t>& smaller
    ){
        std::vector<std::uint32_t> result(larger.size(), 0);
        std::uint64_t borrow = 0;
        for(std::size_t index = 0; index < larger.size(); ++index){
            const std::uint64_t subtrahend = borrow
                + (index < smaller.size() ? smaller[index] : 0);
            const std::uint64_t minuend = larger[index];
            if(minuend < subtrahend){
                result[index] = static_cast<std::uint32_t>(
                    minuend + limb_base - subtrahend
                );
                borrow = 1;
            }else{
                result[index] = static_cast<std::uint32_t>(
                    minuend - subtrahend
                );
                borrow = 0;
            }
        }
        return result;
    }

    static void trim_magnitude(std::vector<std::uint32_t>& value){
        while(!value.empty() && value.back() == 0) value.pop_back();
    }

    struct SignedMagnitude{
        std::vector<std::uint32_t> magnitude;
        bool negative = false;
    };

    static int compare_magnitude_vectors(
        const std::vector<std::uint32_t>& left,
        const std::vector<std::uint32_t>& right
    ){
        if(left.size() != right.size()){
            return left.size() < right.size() ? -1 : 1;
        }
        for(std::size_t index = left.size(); index-- > 0;){
            if(left[index] != right[index]){
                return left[index] < right[index] ? -1 : 1;
            }
        }
        return 0;
    }

    static void normalize_signed_magnitude(SignedMagnitude& value){
        trim_magnitude(value.magnitude);
        if(value.magnitude.empty()) value.negative = false;
    }

    static SignedMagnitude signed_magnitude(
        const std::vector<std::uint32_t>& magnitude
    ){
        SignedMagnitude result{magnitude, false};
        normalize_signed_magnitude(result);
        return result;
    }

    static SignedMagnitude negate_signed_magnitude(SignedMagnitude value){
        if(!value.magnitude.empty()) value.negative = !value.negative;
        return value;
    }

    static SignedMagnitude add_signed_magnitudes(
        const SignedMagnitude& left,
        const SignedMagnitude& right
    ){
        if(left.magnitude.empty()) return right;
        if(right.magnitude.empty()) return left;
        SignedMagnitude result;
        if(left.negative == right.negative){
            result.magnitude = add_magnitudes(
                left.magnitude,
                right.magnitude
            );
            result.negative = left.negative;
        }else{
            const int order = compare_magnitude_vectors(
                left.magnitude,
                right.magnitude
            );
            if(order == 0) return {};
            if(order > 0){
                result.magnitude = subtract_magnitudes(
                    left.magnitude,
                    right.magnitude
                );
                result.negative = left.negative;
            }else{
                result.magnitude = subtract_magnitudes(
                    right.magnitude,
                    left.magnitude
                );
                result.negative = right.negative;
            }
        }
        normalize_signed_magnitude(result);
        return result;
    }

    static SignedMagnitude subtract_signed_magnitudes(
        const SignedMagnitude& left,
        SignedMagnitude right
    ){
        return add_signed_magnitudes(
            left,
            negate_signed_magnitude(std::move(right))
        );
    }

    static SignedMagnitude multiply_signed_magnitude_small(
        const SignedMagnitude& value,
        std::uint32_t factor
    ){
        if(value.magnitude.empty() || factor == 0) return {};
        SignedMagnitude result;
        result.magnitude.resize(value.magnitude.size() + 1, 0);
        std::uint64_t carry = 0;
        for(std::size_t index = 0; index < value.magnitude.size(); ++index){
            const std::uint64_t current =
                static_cast<std::uint64_t>(value.magnitude[index]) * factor
                + carry;
            result.magnitude[index] = static_cast<std::uint32_t>(current);
            carry = current >> 32;
        }
        result.magnitude[value.magnitude.size()] =
            static_cast<std::uint32_t>(carry);
        result.negative = value.negative;
        normalize_signed_magnitude(result);
        return result;
    }

    static SignedMagnitude divide_signed_magnitude_exact(
        SignedMagnitude value,
        std::uint32_t divisor
    ){
        std::uint64_t remainder = 0;
        for(std::size_t index = value.magnitude.size(); index-- > 0;){
            const std::uint64_t current =
                (remainder << 32) | value.magnitude[index];
            value.magnitude[index] =
                static_cast<std::uint32_t>(current / divisor);
            remainder = current % divisor;
        }
        if(remainder != 0){
            throw std::logic_error(
                "ExactInteger Toom-Cook interpolation was not exact"
            );
        }
        normalize_signed_magnitude(value);
        return value;
    }

    static SignedMagnitude multiply_signed_magnitudes(
        const SignedMagnitude& left,
        const SignedMagnitude& right
    ){
        SignedMagnitude result;
        result.magnitude = multiply_magnitudes(
            left.magnitude,
            right.magnitude
        );
        result.negative = !result.magnitude.empty()
            && left.negative != right.negative;
        return result;
    }

    static std::vector<std::uint32_t> toom_cook_3_multiply(
        const std::vector<std::uint32_t>& left,
        const std::vector<std::uint32_t>& right
    ){
        const std::size_t longer = std::max(left.size(), right.size());
        const std::size_t split = longer / 3 + (longer % 3 != 0);

        const SignedMagnitude left_0 = signed_magnitude(
            limb_range(left, 0, split)
        );
        const SignedMagnitude left_1 = signed_magnitude(
            limb_range(left, split, split * 2)
        );
        const SignedMagnitude left_2 = signed_magnitude(
            limb_range(left, split * 2, left.size())
        );
        const SignedMagnitude right_0 = signed_magnitude(
            limb_range(right, 0, split)
        );
        const SignedMagnitude right_1 = signed_magnitude(
            limb_range(right, split, split * 2)
        );
        const SignedMagnitude right_2 = signed_magnitude(
            limb_range(right, split * 2, right.size())
        );

        const SignedMagnitude left_at_1 = add_signed_magnitudes(
            add_signed_magnitudes(left_0, left_1),
            left_2
        );
        const SignedMagnitude right_at_1 = add_signed_magnitudes(
            add_signed_magnitudes(right_0, right_1),
            right_2
        );
        const SignedMagnitude left_at_minus_1 = add_signed_magnitudes(
            subtract_signed_magnitudes(left_0, left_1),
            left_2
        );
        const SignedMagnitude right_at_minus_1 = add_signed_magnitudes(
            subtract_signed_magnitudes(right_0, right_1),
            right_2
        );
        const SignedMagnitude left_at_2 = add_signed_magnitudes(
            add_signed_magnitudes(
                left_0,
                multiply_signed_magnitude_small(left_1, 2)
            ),
            multiply_signed_magnitude_small(left_2, 4)
        );
        const SignedMagnitude right_at_2 = add_signed_magnitudes(
            add_signed_magnitudes(
                right_0,
                multiply_signed_magnitude_small(right_1, 2)
            ),
            multiply_signed_magnitude_small(right_2, 4)
        );

        const SignedMagnitude value_0 = multiply_signed_magnitudes(
            left_0,
            right_0
        );
        const SignedMagnitude value_1 = multiply_signed_magnitudes(
            left_at_1,
            right_at_1
        );
        const SignedMagnitude value_minus_1 = multiply_signed_magnitudes(
            left_at_minus_1,
            right_at_minus_1
        );
        const SignedMagnitude value_2 = multiply_signed_magnitudes(
            left_at_2,
            right_at_2
        );
        const SignedMagnitude value_infinity = multiply_signed_magnitudes(
            left_2,
            right_2
        );

        // For c0+c1*x+...+c4*x^4, (v1-v(-1))/2 is c1+c3.
        // The even coefficients follow from (v1+v(-1))/2, and v2 then
        // separates c1 from c3.  Every division below is exact over the
        // integers, including when an evaluation value is negative.

        const SignedMagnitude odd_sum = divide_signed_magnitude_exact(
            subtract_signed_magnitudes(value_1, value_minus_1),
            2
        );
        SignedMagnitude coefficient_2 = subtract_signed_magnitudes(
            divide_signed_magnitude_exact(
                add_signed_magnitudes(value_1, value_minus_1),
                2
            ),
            value_0
        );
        coefficient_2 = subtract_signed_magnitudes(
            coefficient_2,
            value_infinity
        );

        SignedMagnitude weighted_odd = subtract_signed_magnitudes(
            value_2,
            value_0
        );
        weighted_odd = subtract_signed_magnitudes(
            weighted_odd,
            multiply_signed_magnitude_small(coefficient_2, 4)
        );
        weighted_odd = subtract_signed_magnitudes(
            weighted_odd,
            multiply_signed_magnitude_small(value_infinity, 16)
        );
        weighted_odd = divide_signed_magnitude_exact(
            std::move(weighted_odd),
            2
        );
        const SignedMagnitude coefficient_3 = divide_signed_magnitude_exact(
            subtract_signed_magnitudes(weighted_odd, odd_sum),
            3
        );
        const SignedMagnitude coefficient_1 = subtract_signed_magnitudes(
            odd_sum,
            coefficient_3
        );

        if(value_0.negative || coefficient_1.negative
            || coefficient_2.negative || coefficient_3.negative
            || value_infinity.negative){
            throw std::logic_error(
                "ExactInteger Toom-Cook interpolation became negative"
            );
        }
        std::vector<std::uint32_t> result;
        result.reserve(left.size() + right.size());
        add_shifted_magnitude(result, value_0.magnitude, 0);
        add_shifted_magnitude(result, coefficient_1.magnitude, split);
        add_shifted_magnitude(result, coefficient_2.magnitude, split * 2);
        add_shifted_magnitude(result, coefficient_3.magnitude, split * 3);
        add_shifted_magnitude(result, value_infinity.magnitude, split * 4);
        trim_magnitude(result);
        return result;
    }

    static std::vector<std::uint32_t> schoolbook_multiply(
        const std::vector<std::uint32_t>& left,
        const std::vector<std::uint32_t>& right
    ){
        if(left.empty() || right.empty()) return {};
        std::vector<std::uint32_t> product(
            left.size() + right.size(), 0
        );
        for(std::size_t left_index = 0;
            left_index < left.size();
            ++left_index){
            std::uint64_t carry = 0;
            for(std::size_t right_index = 0;
                right_index < right.size();
                ++right_index){
                const std::size_t destination =
                    left_index + right_index;
                const std::uint64_t current =
                    static_cast<std::uint64_t>(left[left_index])
                        * right[right_index]
                    + product[destination] + carry;
                product[destination] =
                    static_cast<std::uint32_t>(current);
                carry = current >> 32;
            }
            product[left_index + right.size()] =
                static_cast<std::uint32_t>(carry);
        }
        trim_magnitude(product);
        return product;
    }

    static constexpr std::uint64_t goldilocks_modulus =
        18'446'744'069'414'584'321ULL;
    static constexpr std::uint64_t goldilocks_primitive_root = 7;
    static constexpr std::uint64_t goldilocks_maximum_transform_size =
        std::uint64_t{1} << 32;

#if defined(__SIZEOF_INT128__)
    static std::uint64_t goldilocks_reduce(__uint128_t value){
        constexpr std::uint64_t low_mask =
            (std::uint64_t{1} << 32) - 1;
        const std::uint64_t low = static_cast<std::uint64_t>(value);
        const std::uint64_t high = static_cast<std::uint64_t>(value >> 64);
        __uint128_t reduced = static_cast<__uint128_t>(low)
            + static_cast<__uint128_t>(high & low_mask) * low_mask
            + goldilocks_modulus - (high >> 32);
        if(reduced >= goldilocks_modulus) reduced -= goldilocks_modulus;
        if(reduced >= goldilocks_modulus) reduced -= goldilocks_modulus;
        return static_cast<std::uint64_t>(reduced);
    }
#else
#error "ExactInteger requires GCC 13 with unsigned 128-bit integers"
#endif

    static std::uint64_t goldilocks_multiply(
        std::uint64_t left,
        std::uint64_t right
    ){
        return goldilocks_reduce(static_cast<__uint128_t>(left) * right);
    }

    static std::uint64_t goldilocks_power(
        std::uint64_t base,
        std::uint64_t exponent
    ){
        std::uint64_t result = 1;
        while(exponent != 0){
            if((exponent & 1U) != 0){
                result = goldilocks_multiply(result, base);
            }
            base = goldilocks_multiply(base, base);
            exponent >>= 1;
        }
        return result;
    }

    static std::uint64_t goldilocks_add(
        std::uint64_t left,
        std::uint64_t right
    ){
        const std::uint64_t complement = goldilocks_modulus - right;
        return left >= complement ? left - complement : left + right;
    }

    static std::uint64_t goldilocks_subtract(
        std::uint64_t left,
        std::uint64_t right
    ){
        return left >= right
            ? left - right
            : goldilocks_modulus - (right - left);
    }

    static void goldilocks_transform(
        std::vector<std::uint64_t>& values,
        bool inverse
    ){
        const std::size_t size = values.size();
        for(std::size_t index = 1, reversed = 0; index < size; ++index){
            std::size_t bit = size >> 1;
            while((reversed & bit) != 0){
                reversed ^= bit;
                bit >>= 1;
            }
            reversed ^= bit;
            if(index < reversed) std::swap(values[index], values[reversed]);
        }
        for(std::size_t length = 2; length <= size; length <<= 1){
            std::uint64_t root = goldilocks_power(
                goldilocks_primitive_root,
                (goldilocks_modulus - 1) / length
            );
            if(inverse){
                root = goldilocks_power(root, goldilocks_modulus - 2);
            }
            const std::size_t half = length >> 1;
            for(std::size_t block = 0; block < size; block += length){
                std::uint64_t factor = 1;
                for(std::size_t offset = 0; offset < half; ++offset){
                    const std::uint64_t even = values[block + offset];
                    const std::uint64_t odd = goldilocks_multiply(
                        factor, values[block + offset + half]
                    );
                    values[block + offset] = goldilocks_add(even, odd);
                    values[block + offset + half] =
                        goldilocks_subtract(even, odd);
                    factor = goldilocks_multiply(factor, root);
                }
            }
            if(length == size) break;
        }
        if(inverse){
            const std::uint64_t inverse_size = goldilocks_power(
                static_cast<std::uint64_t>(size),
                goldilocks_modulus - 2
            );
            for(std::uint64_t& value: values){
                value = goldilocks_multiply(value, inverse_size);
            }
        }
    }

    static std::vector<std::uint32_t> limbs_to_ntt_digits(
        const std::vector<std::uint32_t>& limbs
    ){
        constexpr unsigned digit_bits = 15;
        constexpr std::uint64_t digit_mask =
            (std::uint64_t{1} << digit_bits) - 1;
        std::vector<std::uint32_t> digits;
        const __uint128_t digit_count =
            (static_cast<__uint128_t>(limbs.size()) * 32
                + digit_bits - 1) / digit_bits;
        if(digit_count > digits.max_size()){
            throw std::length_error("ExactInteger NTT input is too large");
        }
        digits.reserve(static_cast<std::size_t>(digit_count));
        std::uint64_t buffer = 0;
        unsigned buffered_bits = 0;
        for(const std::uint32_t limb: limbs){
            buffer |= static_cast<std::uint64_t>(limb) << buffered_bits;
            buffered_bits += 32;
            while(buffered_bits >= digit_bits){
                digits.push_back(static_cast<std::uint32_t>(
                    buffer & digit_mask
                ));
                buffer >>= digit_bits;
                buffered_bits -= digit_bits;
            }
        }
        if(buffered_bits != 0){
            digits.push_back(static_cast<std::uint32_t>(buffer));
        }
        while(!digits.empty() && digits.back() == 0) digits.pop_back();
        return digits;
    }

    static std::vector<std::uint64_t> goldilocks_convolution(
        const std::vector<std::uint32_t>& left,
        const std::vector<std::uint32_t>& right,
        std::uint32_t maximum_digit
    ){
        if(left.empty() || right.empty()) return {};
        const std::size_t maximum_size =
            (std::numeric_limits<std::size_t>::max)();
        if(left.size() > maximum_size - (right.size() - 1)){
            throw std::length_error("ExactInteger convolution is too large");
        }
        const std::size_t coefficient_count =
            left.size() + right.size() - 1;
        if(coefficient_count > goldilocks_maximum_transform_size){
            throw std::length_error("ExactInteger NTT input is too large");
        }
        const std::uint64_t transform_size_64 = std::bit_ceil(
            static_cast<std::uint64_t>(coefficient_count)
        );
        std::vector<std::uint64_t> capacity_probe;
        if(transform_size_64 > capacity_probe.max_size()){
            throw std::length_error("ExactInteger NTT input is too large");
        }
#if defined(__SIZEOF_INT128__)
        const __uint128_t coefficient_bound =
            static_cast<__uint128_t>(std::min(left.size(), right.size()))
            * maximum_digit * maximum_digit;
        if(coefficient_bound >= goldilocks_modulus){
            throw std::length_error(
                "ExactInteger NTT coefficient is too large"
            );
        }
#endif
        const std::size_t transform_size =
            static_cast<std::size_t>(transform_size_64);
        std::vector<std::uint64_t> left_values(transform_size, 0);
        std::vector<std::uint64_t> right_values(transform_size, 0);
        std::copy(left.begin(), left.end(), left_values.begin());
        std::copy(right.begin(), right.end(), right_values.begin());
        goldilocks_transform(left_values, false);
        goldilocks_transform(right_values, false);
        for(std::size_t index = 0; index < transform_size; ++index){
            left_values[index] = goldilocks_multiply(
                left_values[index], right_values[index]
            );
        }
        goldilocks_transform(left_values, true);
        left_values.resize(coefficient_count);
        return left_values;
    }

    static std::vector<std::uint32_t> ntt_digits_to_limbs(
        const std::vector<std::uint64_t>& coefficients
    ){
        constexpr unsigned digit_bits = 15;
        constexpr std::uint64_t digit_mask =
            (std::uint64_t{1} << digit_bits) - 1;
        std::vector<std::uint32_t> digits;
        if(coefficients.size() > digits.max_size() - 4){
            throw std::length_error("ExactInteger NTT output is too large");
        }
        digits.reserve(coefficients.size() + 4);
        std::uint64_t carry = 0;
        for(const std::uint64_t coefficient: coefficients){
            const std::uint64_t current = coefficient + carry;
            digits.push_back(static_cast<std::uint32_t>(current & digit_mask));
            carry = current >> digit_bits;
        }
        while(carry != 0){
            digits.push_back(static_cast<std::uint32_t>(carry & digit_mask));
            carry >>= digit_bits;
        }
        std::vector<std::uint32_t> limbs;
        const __uint128_t limb_count =
            (static_cast<__uint128_t>(digits.size()) * digit_bits + 31) / 32;
        if(limb_count > limbs.max_size()){
            throw std::length_error("ExactInteger NTT output is too large");
        }
        limbs.reserve(static_cast<std::size_t>(limb_count));
        std::uint64_t buffer = 0;
        unsigned buffered_bits = 0;
        for(const std::uint32_t digit: digits){
            buffer |= static_cast<std::uint64_t>(digit) << buffered_bits;
            buffered_bits += digit_bits;
            if(buffered_bits >= 32){
                limbs.push_back(static_cast<std::uint32_t>(buffer));
                buffer >>= 32;
                buffered_bits -= 32;
            }
        }
        if(buffered_bits != 0){
            limbs.push_back(static_cast<std::uint32_t>(buffer));
        }
        trim_magnitude(limbs);
        return limbs;
    }

    static bool ntt_multiply_supported(
        const std::vector<std::uint32_t>& left,
        const std::vector<std::uint32_t>& right
    ){
        if(left.empty() || right.empty()) return true;
#if defined(__SIZEOF_INT128__)
        const __uint128_t left_digits =
            (static_cast<__uint128_t>(left.size()) * 32 + 14) / 15;
        const __uint128_t right_digits =
            (static_cast<__uint128_t>(right.size()) * 32 + 14) / 15;
        return left_digits + right_digits - 1
            <= goldilocks_maximum_transform_size;
#else
        return false;
#endif
    }

    static std::vector<std::uint32_t> ntt_multiply(
        const std::vector<std::uint32_t>& left,
        const std::vector<std::uint32_t>& right
    ){
        constexpr std::uint32_t maximum_digit =
            (std::uint32_t{1} << 15) - 1;
        const auto left_digits = limbs_to_ntt_digits(left);
        const auto right_digits = limbs_to_ntt_digits(right);
        return ntt_digits_to_limbs(goldilocks_convolution(
            left_digits, right_digits, maximum_digit
        ));
    }

    static std::vector<std::uint32_t> limb_range(
        const std::vector<std::uint32_t>& value,
        std::size_t first,
        std::size_t last
    ){
        first = std::min(first, value.size());
        last = std::min(last, value.size());
        if(first >= last) return {};
        std::vector<std::uint32_t> result(
            value.begin() + static_cast<std::ptrdiff_t>(first),
            value.begin() + static_cast<std::ptrdiff_t>(last)
        );
        trim_magnitude(result);
        return result;
    }

    static void add_shifted_magnitude(
        std::vector<std::uint32_t>& destination,
        const std::vector<std::uint32_t>& addition,
        std::size_t shift
    ){
        if(addition.empty()) return;
        const std::size_t required = shift + addition.size();
        if(destination.size() <= required){
            destination.resize(required + 1, 0);
        }
        std::uint64_t carry = 0;
        for(std::size_t index = 0; index < addition.size(); ++index){
            const std::size_t position = shift + index;
            const std::uint64_t sum =
                static_cast<std::uint64_t>(destination[position])
                + addition[index] + carry;
            destination[position] = static_cast<std::uint32_t>(sum);
            carry = sum >> 32;
        }
        std::size_t position = required;
        while(carry != 0){
            const std::uint64_t sum =
                static_cast<std::uint64_t>(destination[position]) + carry;
            destination[position] = static_cast<std::uint32_t>(sum);
            carry = sum >> 32;
            ++position;
            if(carry != 0 && position == destination.size()){
                destination.push_back(0);
            }
        }
        trim_magnitude(destination);
    }

    static std::vector<std::uint32_t> multiply_magnitudes(
        const std::vector<std::uint32_t>& left,
        const std::vector<std::uint32_t>& right
    ){
        if(left.empty() || right.empty()) return {};
        constexpr std::size_t karatsuba_threshold = 32;
        constexpr std::size_t toom_cook_3_threshold = 192;
        constexpr std::size_t ntt_threshold = 512;
        const std::size_t shorter = std::min(left.size(), right.size());
        const std::size_t longer = std::max(left.size(), right.size());
        const std::size_t toom_split =
            longer / 3 + (longer % 3 != 0);
        if(shorter <= karatsuba_threshold){
            return schoolbook_multiply(left, right);
        }

        // A single exact transform over the 64-bit Goldilocks prime handles
        // every practical allocation while keeping all coefficients unique.
        if(shorter >= ntt_threshold && ntt_multiply_supported(left, right)){
            return ntt_multiply(left, right);
        }

        // Three non-trivial chunks on both sides keep all five Toom-Cook
        // products useful.  More uneven inputs stay on the Karatsuba path.
        // At 192 limbs, the five smaller products amortize evaluation,
        // interpolation, and allocation without penalizing small values.
        if(shorter >= toom_cook_3_threshold
            && shorter > toom_split * 2){
            return toom_cook_3_multiply(left, right);
        }

        const std::size_t split = longer / 2;
        const auto left_low = limb_range(left, 0, split);
        const auto left_high = limb_range(left, split, left.size());
        const auto right_low = limb_range(right, 0, split);
        const auto right_high = limb_range(right, split, right.size());

        const auto low = multiply_magnitudes(left_low, right_low);
        const auto high = multiply_magnitudes(left_high, right_high);
        auto left_sum = add_magnitudes(left_low, left_high);
        auto right_sum = add_magnitudes(right_low, right_high);
        trim_magnitude(left_sum);
        trim_magnitude(right_sum);
        auto middle = multiply_magnitudes(left_sum, right_sum);
        middle = subtract_magnitudes(middle, low);
        trim_magnitude(middle);
        middle = subtract_magnitudes(middle, high);
        trim_magnitude(middle);

        std::vector<std::uint32_t> result;
        result.reserve(left.size() + right.size());
        add_shifted_magnitude(result, low, 0);
        add_shifted_magnitude(result, middle, split);
        add_shifted_magnitude(result, high, split * 2);
        trim_magnitude(result);

        return result;
    }

    static void trim_decimal(DecimalMagnitude& value){
        while(!value.empty() && value.back() == 0) value.pop_back();
    }

    static DecimalMagnitude add_decimals(
        const DecimalMagnitude& left,
        const DecimalMagnitude& right
    ){
        const std::size_t size = std::max(left.size(), right.size());
        DecimalMagnitude result;
        if(size == result.max_size()){
            throw std::length_error(
                "ExactInteger decimal conversion is too large"
            );
        }
        result.assign(size + 1, 0);
        std::uint64_t carry = 0;
        for(std::size_t index = 0; index < size; ++index){
            const std::uint64_t sum = carry
                + (index < left.size() ? left[index] : 0)
                + (index < right.size() ? right[index] : 0);
            result[index] = static_cast<std::uint32_t>(sum % decimal_base);
            carry = sum / decimal_base;
        }
        result[size] = static_cast<std::uint32_t>(carry);
        trim_decimal(result);
        return result;
    }

    static DecimalMagnitude subtract_decimals(
        const DecimalMagnitude& larger,
        const DecimalMagnitude& smaller
    ){
        DecimalMagnitude result(larger.size(), 0);
        std::uint64_t borrow = 0;
        for(std::size_t index = 0; index < larger.size(); ++index){
            const std::uint64_t subtrahend = borrow
                + (index < smaller.size() ? smaller[index] : 0);
            const std::uint64_t minuend = larger[index];
            if(minuend < subtrahend){
                result[index] = static_cast<std::uint32_t>(
                    minuend + decimal_base - subtrahend
                );
                borrow = 1;
            }else{
                result[index] = static_cast<std::uint32_t>(
                    minuend - subtrahend
                );
                borrow = 0;
            }
        }
        trim_decimal(result);
        return result;
    }

    static DecimalMagnitude schoolbook_multiply_decimals(
        const DecimalMagnitude& left,
        const DecimalMagnitude& right
    ){
        if(left.empty() || right.empty()) return {};
        DecimalMagnitude product;
        if(left.size() >= product.max_size()
            || right.size() > product.max_size() - left.size() - 1){
            throw std::length_error(
                "ExactInteger decimal conversion is too large"
            );
        }
        product.assign(left.size() + right.size() + 1, 0);
        for(std::size_t left_index = 0;
            left_index < left.size();
            ++left_index){
            std::uint64_t carry = 0;
            for(std::size_t right_index = 0;
                right_index < right.size();
                ++right_index){
                const std::size_t destination = left_index + right_index;
                const std::uint64_t current =
                    static_cast<std::uint64_t>(left[left_index])
                        * right[right_index]
                    + product[destination] + carry;
                product[destination] =
                    static_cast<std::uint32_t>(current % decimal_base);
                carry = current / decimal_base;
            }
            std::size_t destination = left_index + right.size();
            while(carry != 0){
                const std::uint64_t current =
                    static_cast<std::uint64_t>(product[destination]) + carry;
                product[destination] =
                    static_cast<std::uint32_t>(current % decimal_base);
                carry = current / decimal_base;
                ++destination;
            }
        }
        trim_decimal(product);
        return product;
    }

    static DecimalMagnitude decimal_range(
        const DecimalMagnitude& value,
        std::size_t first,
        std::size_t last
    ){
        first = std::min(first, value.size());
        last = std::min(last, value.size());
        if(first >= last) return {};
        return DecimalMagnitude(
            value.begin() + static_cast<std::ptrdiff_t>(first),
            value.begin() + static_cast<std::ptrdiff_t>(last)
        );
    }

    static void add_shifted_decimal(
        DecimalMagnitude& destination,
        const DecimalMagnitude& addition,
        std::size_t shift
    ){
        if(addition.empty()) return;
        if(shift >= destination.max_size()
            || addition.size() >= destination.max_size() - shift){
            throw std::length_error(
                "ExactInteger decimal conversion is too large"
            );
        }
        const std::size_t required = shift + addition.size();
        if(destination.size() <= required){
            destination.resize(required + 1, 0);
        }
        std::uint64_t carry = 0;
        for(std::size_t index = 0; index < addition.size(); ++index){
            const std::size_t position = shift + index;
            const std::uint64_t sum =
                static_cast<std::uint64_t>(destination[position])
                + addition[index] + carry;
            destination[position] =
                static_cast<std::uint32_t>(sum % decimal_base);
            carry = sum / decimal_base;
        }
        std::size_t position = required;
        while(carry != 0){
            const std::uint64_t sum =
                static_cast<std::uint64_t>(destination[position]) + carry;
            destination[position] =
                static_cast<std::uint32_t>(sum % decimal_base);
            carry = sum / decimal_base;
            ++position;
            if(carry != 0 && position == destination.size()){
                destination.push_back(0);
            }
        }
        trim_decimal(destination);
    }

    static std::vector<std::uint32_t> decimals_to_ntt_digits(
        const DecimalMagnitude& value
    ){
        constexpr std::uint32_t digit_base = 1'000;
        std::vector<std::uint32_t> digits;
        if(value.size() > digits.max_size() / 3){
            throw std::length_error(
                "ExactInteger decimal conversion is too large"
            );
        }
        digits.reserve(value.size() * 3);
        for(std::uint32_t chunk: value){
            digits.push_back(chunk % digit_base);
            chunk /= digit_base;
            digits.push_back(chunk % digit_base);
            digits.push_back(chunk / digit_base);
        }
        while(!digits.empty() && digits.back() == 0) digits.pop_back();
        return digits;
    }

    static DecimalMagnitude ntt_digits_to_decimals(
        const std::vector<std::uint64_t>& coefficients
    ){
        constexpr std::uint32_t digit_base = 1'000;
        std::vector<std::uint32_t> digits;
        if(coefficients.size() > digits.max_size() - 8){
            throw std::length_error(
                "ExactInteger decimal conversion is too large"
            );
        }
        digits.reserve(coefficients.size() + 8);
#if defined(__SIZEOF_INT128__)
        __uint128_t carry = 0;
        for(const std::uint64_t coefficient: coefficients){
            const __uint128_t current = coefficient + carry;
            digits.push_back(static_cast<std::uint32_t>(
                current % digit_base
            ));
            carry = current / digit_base;
        }
        while(carry != 0){
            digits.push_back(static_cast<std::uint32_t>(
                carry % digit_base
            ));
            carry /= digit_base;
        }
#endif
        DecimalMagnitude result;
        result.reserve(digits.size() / 3 + (digits.size() % 3 != 0));
        for(std::size_t index = 0; index < digits.size(); index += 3){
            std::uint32_t chunk = digits[index];
            if(index + 1 < digits.size()){
                chunk += digits[index + 1] * digit_base;
            }
            if(index + 2 < digits.size()){
                chunk += digits[index + 2] * digit_base * digit_base;
            }
            result.push_back(chunk);
        }
        trim_decimal(result);
        return result;
    }

    static DecimalMagnitude ntt_multiply_decimals(
        const DecimalMagnitude& left,
        const DecimalMagnitude& right
    ){
        constexpr std::uint32_t maximum_digit = 999;
        const auto left_digits = decimals_to_ntt_digits(left);
        const auto right_digits = decimals_to_ntt_digits(right);
        return ntt_digits_to_decimals(goldilocks_convolution(
            left_digits, right_digits, maximum_digit
        ));
    }

    static DecimalMagnitude multiply_decimals(
        const DecimalMagnitude& left,
        const DecimalMagnitude& right
    ){
        if(left.empty() || right.empty()) return {};
        constexpr std::size_t karatsuba_threshold = 32;
        constexpr std::size_t ntt_threshold = 256;
        const std::size_t shorter = std::min(left.size(), right.size());
        const std::size_t longer = std::max(left.size(), right.size());
        const bool ntt_supported =
            (static_cast<__uint128_t>(left.size()) + right.size()) * 3
                <= goldilocks_maximum_transform_size;
        if(shorter >= ntt_threshold && ntt_supported){
            return ntt_multiply_decimals(left, right);
        }
        if(shorter <= karatsuba_threshold || longer / shorter >= 2){
            return schoolbook_multiply_decimals(left, right);
        }

        const std::size_t split = longer / 2;
        const auto left_low = decimal_range(left, 0, split);
        const auto left_high = decimal_range(left, split, left.size());
        const auto right_low = decimal_range(right, 0, split);
        const auto right_high = decimal_range(right, split, right.size());

        const auto low = multiply_decimals(left_low, right_low);
        const auto high = multiply_decimals(left_high, right_high);
        const auto left_sum = add_decimals(left_low, left_high);
        const auto right_sum = add_decimals(right_low, right_high);
        auto middle = multiply_decimals(left_sum, right_sum);
        middle = subtract_decimals(middle, low);
        middle = subtract_decimals(middle, high);

        DecimalMagnitude result;
        add_shifted_decimal(result, low, 0);
        add_shifted_decimal(result, middle, split);
        add_shifted_decimal(result, high, split * 2);
        trim_decimal(result);
        return result;
    }

    DecimalMagnitude decimal_magnitude() const{
        std::vector<DecimalMagnitude> blocks;
        blocks.reserve(limbs_.size());
        for(const std::uint32_t limb: limbs_){
            DecimalMagnitude block{
                static_cast<std::uint32_t>(limb % decimal_base),
                static_cast<std::uint32_t>(limb / decimal_base)
            };
            trim_decimal(block);
            blocks.push_back(std::move(block));
        }

        DecimalMagnitude place_value{
            static_cast<std::uint32_t>(limb_base % decimal_base),
            static_cast<std::uint32_t>(limb_base / decimal_base)
        };
        while(blocks.size() > 1){
            std::vector<DecimalMagnitude> merged;
            merged.reserve(
                blocks.size() / 2 + blocks.size() % 2
            );
            for(std::size_t index = 0; index < blocks.size(); index += 2){
                if(index + 1 == blocks.size()){
                    merged.push_back(std::move(blocks[index]));
                    continue;
                }
                auto high = multiply_decimals(blocks[index + 1], place_value);
                merged.push_back(add_decimals(blocks[index], high));
            }
            blocks = std::move(merged);
            if(blocks.size() > 1){
                place_value = multiply_decimals(place_value, place_value);
            }
        }
        return blocks.empty() ? DecimalMagnitude{} : std::move(blocks.front());
    }

    void add_magnitude_one(){
        std::uint64_t carry = 1;
        for(std::uint32_t& limb: limbs_){
            const std::uint64_t sum = limb + carry;
            limb = static_cast<std::uint32_t>(sum);
            carry = sum >> 32;
            if(carry == 0) return;
        }
        if(carry != 0) limbs_.push_back(static_cast<std::uint32_t>(carry));
    }

    template<class Unsigned>
    Unsigned magnitude_to_unsigned() const{
        static_assert(!std::numeric_limits<Unsigned>::is_signed);
        Unsigned result = 0;
        if constexpr(std::numeric_limits<Unsigned>::digits <= 32){
            if(!limbs_.empty()) result = static_cast<Unsigned>(limbs_[0]);
        }else{
            for(std::size_t index = limbs_.size(); index-- > 0;){
                result = static_cast<Unsigned>(result << 32);
                result = static_cast<Unsigned>(result | limbs_[index]);
            }
        }
        return result;
    }

public:
    ExactInteger() = default;

    template<exact_integer_detail::NativeInteger Integer>
    ExactInteger(Integer value){
        assign_integral(value);
    }

    template<exact_integer_detail::NativeInteger Integer>
    ExactInteger& operator=(Integer value){
        assign_integral(value);
        return *this;
    }

    bool is_zero() const{
        return limbs_.empty();
    }

    bool is_negative() const{
        return negative_;
    }

    std::size_t bit_length() const{
        if(limbs_.empty()) return 0;
        const std::size_t complete_bits = (limbs_.size() - 1) * 32;
        return complete_bits + static_cast<std::size_t>(
            32 - std::countl_zero(limbs_.back())
        );
    }

    ExactInteger absolute() const{
        ExactInteger result = *this;
        result.negative_ = false;
        return result;
    }

    template<exact_integer_detail::NativeInteger Integer>
    Integer checked_to() const{
        using Value = std::remove_cv_t<Integer>;
        if constexpr(std::same_as<Value, bool>){
            if(*this == 0) return false;
            if(*this == 1) return true;
            throw std::overflow_error("ExactInteger does not fit target integer type");
        }else{
            const ExactInteger minimum = std::numeric_limits<Value>::is_signed
                ? ExactInteger((std::numeric_limits<Value>::min)())
                : ExactInteger(0);
            const ExactInteger maximum((std::numeric_limits<Value>::max)());
            if(*this < minimum || *this > maximum){
                throw std::overflow_error(
                    "ExactInteger does not fit target integer type"
                );
            }
            using Unsigned = exact_integer_detail::MakeUnsignedT<Value>;
            const Unsigned magnitude = magnitude_to_unsigned<Unsigned>();
            if constexpr(!std::numeric_limits<Value>::is_signed){
                return static_cast<Value>(magnitude);
            }else{
                if(!negative_) return static_cast<Value>(magnitude);
                const Unsigned minimum_magnitude =
                    static_cast<Unsigned>((std::numeric_limits<Value>::max)())
                    + Unsigned{1};
                if(magnitude == minimum_magnitude){
                    return (std::numeric_limits<Value>::min)();
                }
                return static_cast<Value>(-static_cast<Value>(magnitude));
            }
        }
    }

    std::pair<ExactInteger, std::uint64_t> divmod(
        std::uint64_t positive_divisor
    ) const{
        if(positive_divisor == 0){
            throw std::domain_error("ExactInteger division by zero");
        }
        ExactInteger quotient;
        quotient.limbs_.resize(limbs_.size(), 0);
        std::uint64_t remainder = 0;
        for(std::size_t index = limbs_.size(); index-- > 0;){
            std::uint32_t limb_quotient = 0;
            for(int bit = 31; bit >= 0; --bit){
                const std::uint64_t incoming =
                    (limbs_[index] >> bit) & std::uint32_t{1};
                const std::uint64_t half = positive_divisor / 2;
                const bool subtract = remainder > half
                    || (remainder == half
                        && (positive_divisor % 2 == 0 || incoming != 0));
                if(subtract){
                    if(positive_divisor % 2 == 0){
                        remainder = (remainder - half) * 2 + incoming;
                    }else if(remainder == half){
                        remainder = 0;
                    }else{
                        remainder = (remainder - half) * 2 - 1 + incoming;
                    }
                    limb_quotient |= std::uint32_t{1} << bit;
                }else{
                    remainder = remainder * 2 + incoming;
                }
            }
            quotient.limbs_[index] = limb_quotient;
        }
        quotient.negative_ = negative_;
        quotient.normalize();
        return {std::move(quotient), remainder};
    }

    ExactInteger operator-() const{
        ExactInteger result = *this;
        if(!result.is_zero()) result.negative_ = !result.negative_;
        return result;
    }

    ExactInteger& operator+=(const ExactInteger& other){
        if(other.is_zero()) return *this;
        if(is_zero()){
            *this = other;
            return *this;
        }
        if(negative_ == other.negative_){
            limbs_ = add_magnitudes(limbs_, other.limbs_);
        }else{
            const int order = compare_magnitude(*this, other);
            if(order == 0){
                limbs_.clear();
                negative_ = false;
                return *this;
            }
            if(order > 0){
                limbs_ = subtract_magnitudes(limbs_, other.limbs_);
            }else{
                limbs_ = subtract_magnitudes(other.limbs_, limbs_);
                negative_ = other.negative_;
            }
        }
        normalize();
        return *this;
    }

    template<exact_integer_detail::NativeInteger Integer>
    ExactInteger& operator+=(Integer value){
        return *this += ExactInteger(value);
    }

    ExactInteger& operator-=(const ExactInteger& other){
        return *this += -other;
    }

    template<exact_integer_detail::NativeInteger Integer>
    ExactInteger& operator-=(Integer value){
        return *this -= ExactInteger(value);
    }

    ExactInteger& operator*=(const ExactInteger& other){
        if(is_zero() || other.is_zero()){
            limbs_.clear();
            negative_ = false;
            return *this;
        }
        if(other.limbs_.size() > limbs_.max_size() - limbs_.size()){
            throw std::length_error("ExactInteger multiplication is too large");
        }
        auto product = multiply_magnitudes(limbs_, other.limbs_);
        negative_ = negative_ != other.negative_;
        limbs_ = std::move(product);
        normalize();
        return *this;
    }

    template<exact_integer_detail::NativeInteger Integer>
    ExactInteger& operator*=(Integer value){
        return *this *= ExactInteger(value);
    }

    ExactInteger& operator<<=(std::size_t shift){
        if(is_zero() || shift == 0) return *this;
        const std::size_t word_shift = shift / 32;
        const unsigned bit_shift = static_cast<unsigned>(shift % 32);
        if(limbs_.size() == limbs_.max_size()
            || word_shift > limbs_.max_size() - limbs_.size() - 1){
            throw std::length_error("ExactInteger left shift is too large");
        }
        std::vector<std::uint32_t> shifted(
            word_shift + limbs_.size() + 1, 0
        );
        std::uint64_t carry = 0;
        for(std::size_t index = 0; index < limbs_.size(); ++index){
            const std::uint64_t current =
                (static_cast<std::uint64_t>(limbs_[index]) << bit_shift)
                | carry;
            shifted[word_shift + index] = static_cast<std::uint32_t>(current);
            carry = current >> 32;
        }
        shifted[word_shift + limbs_.size()] =
            static_cast<std::uint32_t>(carry);
        limbs_ = std::move(shifted);
        normalize();
        return *this;
    }

    ExactInteger& operator>>=(std::size_t shift){
        if(is_zero() || shift == 0) return *this;
        const bool was_negative = negative_;
        const std::size_t word_shift = shift / 32;
        const unsigned bit_shift = static_cast<unsigned>(shift % 32);
        bool discarded = false;
        const std::size_t removed_words = std::min(word_shift, limbs_.size());
        for(std::size_t index = 0; index < removed_words; ++index){
            discarded = discarded || limbs_[index] != 0;
        }
        if(word_shift < limbs_.size() && bit_shift != 0){
            const std::uint32_t mask =
                (std::uint32_t{1} << bit_shift) - 1;
            discarded = discarded || (limbs_[word_shift] & mask) != 0;
        }
        if(word_shift >= limbs_.size()){
            limbs_.clear();
        }else{
            const std::size_t new_size = limbs_.size() - word_shift;
            std::vector<std::uint32_t> shifted(new_size, 0);
            for(std::size_t destination = 0; destination < new_size; ++destination){
                const std::size_t source = destination + word_shift;
                std::uint64_t value = limbs_[source] >> bit_shift;
                if(bit_shift != 0 && source + 1 < limbs_.size()){
                    value |= static_cast<std::uint64_t>(limbs_[source + 1])
                        << (32 - bit_shift);
                }
                shifted[destination] = static_cast<std::uint32_t>(value);
            }
            limbs_ = std::move(shifted);
        }
        normalize();
        if(was_negative && discarded){
            add_magnitude_one();
            negative_ = true;
        }
        return *this;
    }

    std::string to_string() const{
        if(is_zero()) return "0";
        const DecimalMagnitude chunks = decimal_magnitude();
        std::string result = negative_ ? "-" : "";
        const std::size_t sign_size = static_cast<std::size_t>(negative_);
        if(chunks.size() <= (result.max_size() - sign_size) / 9){
            result.reserve(chunks.size() * 9 + sign_size);
        }
        result += std::to_string(chunks.back());
        for(std::size_t index = chunks.size() - 1; index-- > 0;){
            std::string chunk = std::to_string(chunks[index]);
            result.append(9 - chunk.size(), '0');
            result += chunk;
        }
        return result;
    }

    friend ExactInteger abs(const ExactInteger& value){
        return value.absolute();
    }

    friend bool operator==(const ExactInteger& left, const ExactInteger& right){
        return left.negative_ == right.negative_ && left.limbs_ == right.limbs_;
    }

    friend std::strong_ordering operator<=> (
        const ExactInteger& left,
        const ExactInteger& right
    ){
        if(left.negative_ != right.negative_){
            return left.negative_ ? std::strong_ordering::less
                                  : std::strong_ordering::greater;
        }
        const int magnitude_order = compare_magnitude(left, right);
        if(magnitude_order == 0) return std::strong_ordering::equal;
        const bool less = left.negative_ ? magnitude_order > 0
                                         : magnitude_order < 0;
        return less ? std::strong_ordering::less
                    : std::strong_ordering::greater;
    }

    friend ExactInteger operator+(ExactInteger left, const ExactInteger& right){
        left += right;
        return left;
    }

    friend ExactInteger operator-(ExactInteger left, const ExactInteger& right){
        left -= right;
        return left;
    }

    friend ExactInteger operator*(ExactInteger left, const ExactInteger& right){
        left *= right;
        return left;
    }

    friend ExactInteger operator<<(ExactInteger value, std::size_t shift){
        value <<= shift;
        return value;
    }

    friend ExactInteger operator>>(ExactInteger value, std::size_t shift){
        value >>= shift;
        return value;
    }

    friend std::ostream& operator<<(std::ostream& stream, const ExactInteger& value){
        return stream << value.to_string();
    }
};


// #line 15 "src/algorithm/math/integer/big_integer.hpp"

class BigInteger{
private:
    ExactInteger value_;

    static ExactInteger parse_decimal(std::string_view text){
        if(text.empty()){
            throw std::invalid_argument("empty BigInteger literal");
        }
        bool negative = false;
        std::size_t position = 0;
        if(text.front() == '+' || text.front() == '-'){
            negative = text.front() == '-';
            position = 1;
        }
        if(position == text.size()){
            throw std::invalid_argument("BigInteger literal has no digits");
        }
        ExactInteger result = 0;
        for(; position < text.size(); ++position){
            const char character = text[position];
            if(character < '0' || character > '9'){
                throw std::invalid_argument("invalid BigInteger decimal digit");
            }
            result *= 10;
            result += character - '0';
        }
        return negative ? -result : result;
    }

public:
    BigInteger() = default;

    template<exact_integer_detail::NativeInteger Integer>
    BigInteger(Integer value): value_(value){}

    explicit BigInteger(std::string_view decimal): value_(parse_decimal(decimal)){}

    explicit BigInteger(const ExactInteger& value): value_(value){}
    explicit BigInteger(ExactInteger&& value): value_(std::move(value)){}

    template<exact_integer_detail::NativeInteger Integer>
    BigInteger& operator=(Integer value){
        value_ = value;
        return *this;
    }

    BigInteger& assign(std::string_view decimal){
        value_ = parse_decimal(decimal);
        return *this;
    }

    bool is_zero() const{
        return value_.is_zero();
    }

    bool is_negative() const{
        return value_.is_negative();
    }

    std::size_t bit_length() const{
        return value_.bit_length();
    }

    BigInteger absolute() const{
        return BigInteger(value_.absolute());
    }

    std::string to_string() const{
        return value_.to_string();
    }

    template<exact_integer_detail::NativeInteger Integer>
    Integer checked_to() const{
        return value_.template checked_to<Integer>();
    }

    const ExactInteger& exact_integer() const noexcept{
        return value_;
    }

    static std::pair<BigInteger, BigInteger> divmod(
        const BigInteger& dividend,
        const BigInteger& divisor
    ){
        if(divisor.is_zero()){
            throw std::domain_error("BigInteger division by zero");
        }
        ExactInteger remainder = dividend.value_.absolute();
        ExactInteger positive_divisor = divisor.value_.absolute();
        ExactInteger quotient = 0;
        if(remainder >= positive_divisor){
            const std::size_t shift =
                remainder.bit_length() - positive_divisor.bit_length();
            ExactInteger shifted_divisor = positive_divisor << shift;
            ExactInteger quotient_bit = ExactInteger(1) << shift;
            while(true){
                if(remainder >= shifted_divisor){
                    remainder -= shifted_divisor;
                    quotient += quotient_bit;
                }
                if(quotient_bit == 1) break;
                shifted_divisor >>= 1;
                quotient_bit >>= 1;
            }
        }
        if(dividend.is_negative() != divisor.is_negative()) quotient = -quotient;
        if(dividend.is_negative()) remainder = -remainder;
        return {
            BigInteger(std::move(quotient)),
            BigInteger(std::move(remainder))
        };
    }

    BigInteger operator-() const{
        return BigInteger(-value_);
    }

    BigInteger& operator+=(const BigInteger& other){
        value_ += other.value_;
        return *this;
    }

    BigInteger& operator-=(const BigInteger& other){
        value_ -= other.value_;
        return *this;
    }

    BigInteger& operator*=(const BigInteger& other){
        value_ *= other.value_;
        return *this;
    }

    BigInteger& operator/=(const BigInteger& other){
        *this = divmod(*this, other).first;
        return *this;
    }

    BigInteger& operator%=(const BigInteger& other){
        *this = divmod(*this, other).second;
        return *this;
    }

    BigInteger& operator<<=(std::size_t shift){
        value_ <<= shift;
        return *this;
    }

    BigInteger& operator>>=(std::size_t shift){
        value_ >>= shift;
        return *this;
    }

    BigInteger& operator++(){
        value_ += 1;
        return *this;
    }

    BigInteger operator++(int){
        BigInteger result = *this;
        ++*this;
        return result;
    }

    BigInteger& operator--(){
        value_ -= 1;
        return *this;
    }

    BigInteger operator--(int){
        BigInteger result = *this;
        --*this;
        return result;
    }

    friend BigInteger abs(const BigInteger& value){
        return value.absolute();
    }

    friend bool operator==(const BigInteger&, const BigInteger&) = default;

    friend std::strong_ordering operator<=> (
        const BigInteger& left,
        const BigInteger& right
    ){
        return left.value_ <=> right.value_;
    }

    friend BigInteger operator+(BigInteger left, const BigInteger& right){
        left += right;
        return left;
    }

    friend BigInteger operator-(BigInteger left, const BigInteger& right){
        left -= right;
        return left;
    }

    friend BigInteger operator*(BigInteger left, const BigInteger& right){
        left *= right;
        return left;
    }

    friend BigInteger operator/(BigInteger left, const BigInteger& right){
        left /= right;
        return left;
    }

    friend BigInteger operator%(BigInteger left, const BigInteger& right){
        left %= right;
        return left;
    }

    friend BigInteger operator<<(BigInteger value, std::size_t shift){
        value <<= shift;
        return value;
    }

    friend BigInteger operator>>(BigInteger value, std::size_t shift){
        value >>= shift;
        return value;
    }

    friend std::ostream& operator<<(std::ostream& stream, const BigInteger& value){
        return stream << value.to_string();
    }

    friend std::istream& operator>>(std::istream& stream, BigInteger& value){
        std::string token;
        if(!(stream >> token)) return stream;
        try{
            value.assign(token);
        }catch(const std::invalid_argument&){
            stream.setstate(std::ios::failbit);
        }
        return stream;
    }
};

BigInteger Biggcd(BigInteger x, BigInteger y){
    if(y>0)return Biggcd(y,x%y);
    return x;
}

BigInteger Biglcm(BigInteger x, BigInteger y){
    return x/Biggcd(x,y)*y;
}

struct edge{
    int u,v,a,b;
};

int main(){
    int N,M;
    cin>>N>>M;
    vector<edge> edges(M);
    BigInteger l=1;
    for(int i=0;i<M;i++){
        cin>>edges[i].u>>edges[i].v>>edges[i].a>>edges[i].b;
        edges[i].u--;
        edges[i].v--;
        l=Biglcm(edges[i].b,l);
    }
    vector<vector<pair<int,BigInteger>>> G(N);
    BigInteger INF=1;
    for(int i=0;i<M;i++){
        G[edges[i].u].push_back({edges[i].v, edges[i].a*l/edges[i].b});
        G[edges[i].v].push_back({edges[i].u, edges[i].a*l/edges[i].b});
        INF+=edges[i].a*l/edges[i].b;
    }
    priority_queue<pair<BigInteger,int>, vector<pair<BigInteger,int>>, greater<pair<BigInteger, int>>> que;
    que.push({BigInteger(0),0});
    vector<BigInteger> ans(N, INF);
    ans[0]=0;
    while(!que.empty()){
        auto [c,now]=que.top();
        que.pop();
        if(ans[now]<c){
            continue;
        }
        for(const auto& [to,cost]: G[now]){
            if(ans[to]>ans[now]+cost){
                ans[to]=ans[now]+cost;
                que.push({ans[to],to});
            }
        }
    }
    for(int i=1;i<N;i++){
        BigInteger g=Biggcd(ans[i],l);
        cout<<ans[i]/g<<" "<<l/g<<"\n";
    }
}
0