Skip to content

sum_two_squares.hpp

SECTIONMath INCLUDEnoya/sum_two_squares.hpp

判断并构造 \(n=a^2+b^2\),同时按二平方和定理统计带符号有序表示数。

\[ \displaystyle n=a^2+b^2 \]

Complexity: Time: Expected integer-factorization time plus O(r log n) to enumerate r representations. Space: O(r + log n).

AC 记录:two_square_sum

跳到代码 · GitHub ↗

Implementation

当前头文件,省略 include guard;依赖见 #include

/// @complexity Time: Expected integer-factorization time plus O(r log n) to
/// enumerate r representations. Space: O(r + log n).

#include "noya/factorize.hpp"
#include "noya/mod_sqrt.hpp"

#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <optional>
#include <utility>
#include <vector>

namespace noya {

/// @brief Decide and construct n = a^2 + b^2 for an unsigned 64-bit integer;
/// also count ordered signed representations using the two-square theorem.
namespace sum_two_squares_internal {

using u64 = std::uint64_t;
using u128 = unsigned __int128;
using i128 = __int128;

inline u64 integer_sqrt(u64 val) {
  u64 rt = u64(std::sqrt(static_cast<long double>(val)));
  while (u128(rt + 1) * (rt + 1) <= val) {
    rt++;
  }
  while (u128(rt) * rt > val) {
    rt--;
  }
  return rt;
}

inline std::pair<u64, u64> multiply(std::pair<u64, u64> lhs,
                                    std::pair<u64, u64> rhs) {
  i128 re = i128(lhs.first) * rhs.first - i128(lhs.second) * rhs.second;
  i128 im = i128(lhs.first) * rhs.second + i128(lhs.second) * rhs.first;
  u64 a = u64(re < 0 ? -re : re);
  u64 b = u64(im < 0 ? -im : im);
  if (a > b) {
    std::swap(a, b);
  }
  return {a, b};
}

inline std::optional<std::pair<u64, u64>> prime_representation(u64 p) {
  assert(p % 4 == 1 && is_prime(p));
  auto sqr = mod_sqrt(p - 1, p);
  assert(sqr.has_value());
  for (u64 rt : {*sqr, p - *sqr}) {
    u64 pre = p;
    u64 cur = rt;
    while (u128(cur) * cur > p) {
      u64 nxt = pre % cur;
      pre = cur;
      cur = nxt;
    }
    u64 sq = p - cur * cur;
    u64 oth = integer_sqrt(sq);
    if (oth * oth == sq) {
      return std::pair<u64, u64>{std::min(cur, oth), std::max(cur, oth)};
    }
  }
  return std::nullopt;
}

inline std::pair<u64, u64> power(std::pair<u64, u64> bas, int exp) {
  std::pair<u64, u64> res{1, 0};
  while (exp > 0) {
    if (exp & 1) {
      res = multiply(res, bas);
    }
    exp >>= 1;
    if (exp > 0) {
      bas = multiply(bas, bas);
    }
  }
  return res;
}

using gaussian = std::pair<i128, i128>;

inline gaussian signed_multiply(gaussian lhs, gaussian rhs) {
  return {lhs.first * rhs.first - lhs.second * rhs.second,
          lhs.first * rhs.second + lhs.second * rhs.first};
}

inline gaussian signed_power(gaussian bas, int exp) {
  gaussian res{1, 0};
  while (exp > 0) {
    if (exp & 1) {
      res = signed_multiply(res, bas);
    }
    exp >>= 1;
    if (exp > 0) {
      bas = signed_multiply(bas, bas);
    }
  }
  return res;
}

} // namespace sum_two_squares_internal

inline bool is_sum_two_squares(std::uint64_t n) {
  if (n == 0) {
    return true;
  }
  for (auto [p, exp] : factorize(n)) {
    if (p % 4 == 3 && exp % 2 == 1) {
      return false;
    }
  }
  return true;
}

/// @brief Return one pair 0 <= a <= b with a^2 + b^2 = n, or nullopt.
inline std::optional<std::pair<std::uint64_t, std::uint64_t>>
sum_two_squares(std::uint64_t n) {
  using namespace sum_two_squares_internal;
  if (n == 0) {
    return std::pair<u64, u64>{0, 0};
  }
  std::pair<u64, u64> res{1, 0};
  for (auto [p, exp] : factorize(n)) {
    if (p == 2) {
      res = multiply(res, power({1, 1}, exp));
    } else if (p % 4 == 1) {
      auto rep = prime_representation(p);
      assert(rep.has_value());
      res = multiply(res, power(*rep, exp));
    } else {
      if (exp % 2 == 1) {
        return std::nullopt;
      }
      u64 scl = 1;
      for (int i = 0; i < exp / 2; i++) {
        scl *= p;
      }
      res = multiply(res, {scl, 0});
    }
  }
  if (res.first > res.second) {
    std::swap(res.first, res.second);
  }
  return res;
}

/// @brief Count integer pairs (a,b), including signs and order, satisfying
/// a^2 + b^2 = n.
inline unsigned __int128 sum_two_squares_representation_count(std::uint64_t n) {
  if (n == 0) {
    return 1;
  }
  unsigned __int128 res = 4;
  for (auto [p, exp] : factorize(n)) {
    if (p % 4 == 3 && exp % 2 == 1) {
      return 0;
    }
    if (p % 4 == 1) {
      res *= exp + 1;
    }
  }
  return res;
}

/// @brief Return every ordered non-negative pair (a,b) with a^2 + b^2 = n.
/// The result is sorted and contains no duplicates. Requires n <= 1e18.
inline std::vector<std::pair<std::uint64_t, std::uint64_t>>
all_sum_two_squares(std::uint64_t n) {
  using namespace sum_two_squares_internal;
  assert(n <= 1000000000000000000ULL);
  if (n == 0) {
    return {{0, 0}};
  }

  auto fs = factorize(n);
  for (auto [p, exp] : fs) {
    if (p % 4 == 3 && exp % 2 == 1) {
      return {};
    }
  }

  std::vector<gaussian> rs{{1, 0}};
  for (auto [p, exp] : fs) {
    if (p % 4 == 3) {
      i128 scl = 1;
      for (int i = 0; i < exp / 2; i++) {
        scl *= p;
      }
      for (auto &[re, im] : rs) {
        re *= scl;
        im *= scl;
      }
      continue;
    }

    gaussian pf;
    if (p == 2) {
      pf = {1, 1};
      gaussian mul = signed_power(pf, exp);
      for (auto &val : rs) {
        val = signed_multiply(val, mul);
      }
      continue;
    }

    auto rep = prime_representation(p);
    assert(rep.has_value());
    pf = {rep->first, rep->second};
    std::vector<gaussian> pw(exp + 1, {1, 0});
    for (int i = 0; i < exp; i++) {
      pw[i + 1] = signed_multiply(pw[i], pf);
    }

    std::vector<gaussian> nxt;
    nxt.reserve(rs.size() * (exp + 1));
    for (gaussian cur : rs) {
      for (int cho = 0; cho <= exp; cho++) {
        gaussian cjg = pw[exp - cho];
        cjg.second = -cjg.second;
        gaussian fct = signed_multiply(pw[cho], cjg);
        nxt.push_back(signed_multiply(cur, fct));
      }
    }
    rs.swap(nxt);
  }

  std::vector<std::pair<u64, u64>> res;
  for (auto [re, im] : rs) {
    while (re <= 0 || im < 0) {
      i128 ore = re;
      re = -im;
      im = ore;
    }
    res.emplace_back(u64(re), u64(im));
    if (im == 0) {
      res.emplace_back(0, u64(re));
    }
  }
  std::sort(res.begin(), res.end());
  res.erase(std::unique(res.begin(), res.end()), res.end());
  return res;
}

} // namespace noya
#ifndef NOYA_SUM_TWO_SQUARES_HPP
#define NOYA_SUM_TWO_SQUARES_HPP 1

/// @complexity Time: Expected integer-factorization time plus O(r log n) to
/// enumerate r representations. Space: O(r + log n).

#include "noya/factorize.hpp"
#include "noya/mod_sqrt.hpp"

#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <optional>
#include <utility>
#include <vector>

namespace noya {

/// @brief Decide and construct n = a^2 + b^2 for an unsigned 64-bit integer;
/// also count ordered signed representations using the two-square theorem.
namespace sum_two_squares_internal {

using u64 = std::uint64_t;
using u128 = unsigned __int128;
using i128 = __int128;

inline u64 integer_sqrt(u64 val) {
  u64 rt = u64(std::sqrt(static_cast<long double>(val)));
  while (u128(rt + 1) * (rt + 1) <= val) {
    rt++;
  }
  while (u128(rt) * rt > val) {
    rt--;
  }
  return rt;
}

inline std::pair<u64, u64> multiply(std::pair<u64, u64> lhs,
                                    std::pair<u64, u64> rhs) {
  i128 re = i128(lhs.first) * rhs.first - i128(lhs.second) * rhs.second;
  i128 im = i128(lhs.first) * rhs.second + i128(lhs.second) * rhs.first;
  u64 a = u64(re < 0 ? -re : re);
  u64 b = u64(im < 0 ? -im : im);
  if (a > b) {
    std::swap(a, b);
  }
  return {a, b};
}

inline std::optional<std::pair<u64, u64>> prime_representation(u64 p) {
  assert(p % 4 == 1 && is_prime(p));
  auto sqr = mod_sqrt(p - 1, p);
  assert(sqr.has_value());
  for (u64 rt : {*sqr, p - *sqr}) {
    u64 pre = p;
    u64 cur = rt;
    while (u128(cur) * cur > p) {
      u64 nxt = pre % cur;
      pre = cur;
      cur = nxt;
    }
    u64 sq = p - cur * cur;
    u64 oth = integer_sqrt(sq);
    if (oth * oth == sq) {
      return std::pair<u64, u64>{std::min(cur, oth), std::max(cur, oth)};
    }
  }
  return std::nullopt;
}

inline std::pair<u64, u64> power(std::pair<u64, u64> bas, int exp) {
  std::pair<u64, u64> res{1, 0};
  while (exp > 0) {
    if (exp & 1) {
      res = multiply(res, bas);
    }
    exp >>= 1;
    if (exp > 0) {
      bas = multiply(bas, bas);
    }
  }
  return res;
}

using gaussian = std::pair<i128, i128>;

inline gaussian signed_multiply(gaussian lhs, gaussian rhs) {
  return {lhs.first * rhs.first - lhs.second * rhs.second,
          lhs.first * rhs.second + lhs.second * rhs.first};
}

inline gaussian signed_power(gaussian bas, int exp) {
  gaussian res{1, 0};
  while (exp > 0) {
    if (exp & 1) {
      res = signed_multiply(res, bas);
    }
    exp >>= 1;
    if (exp > 0) {
      bas = signed_multiply(bas, bas);
    }
  }
  return res;
}

} // namespace sum_two_squares_internal

inline bool is_sum_two_squares(std::uint64_t n) {
  if (n == 0) {
    return true;
  }
  for (auto [p, exp] : factorize(n)) {
    if (p % 4 == 3 && exp % 2 == 1) {
      return false;
    }
  }
  return true;
}

/// @brief Return one pair 0 <= a <= b with a^2 + b^2 = n, or nullopt.
inline std::optional<std::pair<std::uint64_t, std::uint64_t>>
sum_two_squares(std::uint64_t n) {
  using namespace sum_two_squares_internal;
  if (n == 0) {
    return std::pair<u64, u64>{0, 0};
  }
  std::pair<u64, u64> res{1, 0};
  for (auto [p, exp] : factorize(n)) {
    if (p == 2) {
      res = multiply(res, power({1, 1}, exp));
    } else if (p % 4 == 1) {
      auto rep = prime_representation(p);
      assert(rep.has_value());
      res = multiply(res, power(*rep, exp));
    } else {
      if (exp % 2 == 1) {
        return std::nullopt;
      }
      u64 scl = 1;
      for (int i = 0; i < exp / 2; i++) {
        scl *= p;
      }
      res = multiply(res, {scl, 0});
    }
  }
  if (res.first > res.second) {
    std::swap(res.first, res.second);
  }
  return res;
}

/// @brief Count integer pairs (a,b), including signs and order, satisfying
/// a^2 + b^2 = n.
inline unsigned __int128 sum_two_squares_representation_count(std::uint64_t n) {
  if (n == 0) {
    return 1;
  }
  unsigned __int128 res = 4;
  for (auto [p, exp] : factorize(n)) {
    if (p % 4 == 3 && exp % 2 == 1) {
      return 0;
    }
    if (p % 4 == 1) {
      res *= exp + 1;
    }
  }
  return res;
}

/// @brief Return every ordered non-negative pair (a,b) with a^2 + b^2 = n.
/// The result is sorted and contains no duplicates. Requires n <= 1e18.
inline std::vector<std::pair<std::uint64_t, std::uint64_t>>
all_sum_two_squares(std::uint64_t n) {
  using namespace sum_two_squares_internal;
  assert(n <= 1000000000000000000ULL);
  if (n == 0) {
    return {{0, 0}};
  }

  auto fs = factorize(n);
  for (auto [p, exp] : fs) {
    if (p % 4 == 3 && exp % 2 == 1) {
      return {};
    }
  }

  std::vector<gaussian> rs{{1, 0}};
  for (auto [p, exp] : fs) {
    if (p % 4 == 3) {
      i128 scl = 1;
      for (int i = 0; i < exp / 2; i++) {
        scl *= p;
      }
      for (auto &[re, im] : rs) {
        re *= scl;
        im *= scl;
      }
      continue;
    }

    gaussian pf;
    if (p == 2) {
      pf = {1, 1};
      gaussian mul = signed_power(pf, exp);
      for (auto &val : rs) {
        val = signed_multiply(val, mul);
      }
      continue;
    }

    auto rep = prime_representation(p);
    assert(rep.has_value());
    pf = {rep->first, rep->second};
    std::vector<gaussian> pw(exp + 1, {1, 0});
    for (int i = 0; i < exp; i++) {
      pw[i + 1] = signed_multiply(pw[i], pf);
    }

    std::vector<gaussian> nxt;
    nxt.reserve(rs.size() * (exp + 1));
    for (gaussian cur : rs) {
      for (int cho = 0; cho <= exp; cho++) {
        gaussian cjg = pw[exp - cho];
        cjg.second = -cjg.second;
        gaussian fct = signed_multiply(pw[cho], cjg);
        nxt.push_back(signed_multiply(cur, fct));
      }
    }
    rs.swap(nxt);
  }

  std::vector<std::pair<u64, u64>> res;
  for (auto [re, im] : rs) {
    while (re <= 0 || im < 0) {
      i128 ore = re;
      re = -im;
      im = ore;
    }
    res.emplace_back(u64(re), u64(im));
    if (im == 0) {
      res.emplace_back(0, u64(re));
    }
  }
  std::sort(res.begin(), res.end());
  res.erase(std::unique(res.begin(), res.end()), res.end());
  return res;
}

} // namespace noya

#endif // NOYA_SUM_TWO_SQUARES_HPP
#include <algorithm>
#include <array>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <numeric>
#include <optional>
#include <utility>
#include <vector>

/// @complexity Time: Expected integer-factorization time plus O(r log n) to
/// enumerate r representations. Space: O(r + log n).

/// @complexity Time: O(log^3 n) primality testing; Pollard-rho factorization is expected about O(n^(1/4)).
/// Space: O(log n) recursion and factors.

namespace noya {
namespace factorize_internal {

using u64 = std::uint64_t;
using u128 = unsigned __int128;

inline u64 multiply_mod(u64 a, u64 b, u64 mod) {
  return u64(u128(a) * b % mod);
}

inline u64 power_mod(u64 a, u64 exp, u64 mod) {
  u64 res = 1;
  while (exp > 0) {
    if (exp & 1) {
      res = multiply_mod(res, a, mod);
    }
    a = multiply_mod(a, a, mod);
    exp >>= 1;
  }
  return res;
}

inline bool miller_rabin(u64 n) {
  if (n < 2) {
    return false;
  }
  for (u64 p :
       std::array<u64, 12>{2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37}) {
    if (n % p == 0) {
      return n == p;
    }
  }
  int shf = __builtin_ctzll(n - 1);
  u64 odd = (n - 1) >> shf;
  for (u64 bas :
       std::array<u64, 7>{2, 325, 9375, 28178, 450775, 9780504, 1795265022}) {
    if (bas % n == 0) {
      continue;
    }
    u64 val = power_mod(bas % n, odd, n);
    if (val == 1 || val == n - 1) {
      continue;
    }
    bool cmp = true;
    for (int i = 1; i < shf; i++) {
      val = multiply_mod(val, val, n);
      if (val == n - 1) {
        cmp = false;
        break;
      }
    }
    if (cmp) {
      return false;
    }
  }
  return true;
}

inline u64 splitmix64(u64 &st) {
  u64 z = (st += 0x9e3779b97f4a7c15ULL);
  z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ULL;
  z = (z ^ (z >> 27)) * 0x94d049bb133111ebULL;
  return z ^ (z >> 31);
}

inline u64 pollard_rho(u64 n) {
  if (n % 2 == 0) {
    return 2;
  }
  if (n % 3 == 0) {
    return 3;
  }
  static u64 st = 0x123456789abcdef0ULL;
  while (true) {
    u64 y = splitmix64(st) % (n - 1) + 1;
    u64 c = splitmix64(st) % (n - 1) + 1;
    constexpr u64 blk = 128;
    u64 g = 1;
    u64 r = 1;
    u64 q = 1;
    u64 x = 0;
    u64 sy = 0;
    auto nxt = [&](u64 val) {
      return u64((u128(multiply_mod(val, val, n)) + c) % n);
    };
    while (g == 1) {
      x = y;
      for (u64 i = 0; i < r; i++) {
        y = nxt(y);
      }
      for (u64 off = 0; off < r && g == 1; off += blk) {
        sy = y;
        for (u64 i = 0; i < std::min(blk, r - off); i++) {
          y = nxt(y);
          u64 dif = x > y ? x - y : y - x;
          q = multiply_mod(q, dif, n);
        }
        g = std::gcd(q, n);
      }
      r <<= 1;
    }
    if (g == n) {
      do {
        sy = nxt(sy);
        u64 dif = x > sy ? x - sy : sy - x;
        g = std::gcd(dif, n);
      } while (g == 1);
    }
    if (g != n) {
      return g;
    }
  }
}

inline void collect_factors(u64 n, std::vector<u64> &res) {
  if (n == 1) {
    return;
  }
  if (miller_rabin(n)) {
    res.push_back(n);
    return;
  }
  u64 fct = pollard_rho(n);
  collect_factors(fct, res);
  collect_factors(n / fct, res);
}

} // namespace factorize_internal

/// @brief Deterministic Miller-Rabin primality test for unsigned 64-bit
/// integers.
inline bool is_prime(std::uint64_t n) {
  return factorize_internal::miller_rabin(n);
}

/// @brief Return the prime factors of n with multiplicity in increasing order.
inline std::vector<std::uint64_t> prime_factors(std::uint64_t n) {
  assert(n >= 1);
  std::vector<std::uint64_t> res;
  factorize_internal::collect_factors(n, res);
  std::sort(res.begin(), res.end());
  return res;
}

/// @brief Return the prime factorization of n as (prime, exponent) pairs.
inline std::vector<std::pair<std::uint64_t, int>> factorize(std::uint64_t n) {
  std::vector<std::pair<std::uint64_t, int>> res;
  for (std::uint64_t p : prime_factors(n)) {
    if (res.empty() || res.back().first != p) {
      res.emplace_back(p, 1);
    } else {
      res.back().second++;
    }
  }
  return res;
}

} // namespace noya

/// @complexity Time: O(log^2 p).
/// Space: O(1).

namespace noya {

/// @brief Compute the smaller square root modulo a prime, or nullopt if no
/// square root exists.
inline std::optional<std::uint64_t> mod_sqrt(std::uint64_t val,
                                             std::uint64_t mod) {
  assert(mod >= 2 && is_prime(mod));
  val %= mod;
  if (mod == 2 || val == 0) {
    return val;
  }
  using factorize_internal::multiply_mod;
  using factorize_internal::power_mod;
  if (power_mod(val, (mod - 1) / 2, mod) != 1) {
    return std::nullopt;
  }
  if (mod % 4 == 3) {
    std::uint64_t rt = power_mod(val, (mod + 1) / 4, mod);
    return std::min(rt, mod - rt);
  }

  std::uint64_t odd = mod - 1;
  int exp = 0;
  while ((odd & 1) == 0) {
    odd >>= 1;
    exp++;
  }
  std::uint64_t nqr = 2;
  while (power_mod(nqr, (mod - 1) / 2, mod) != mod - 1) {
    nqr++;
  }

  std::uint64_t rt = power_mod(val, (odd + 1) / 2, mod);
  std::uint64_t rem = power_mod(val, odd, mod);
  std::uint64_t ste = power_mod(nqr, odd, mod);
  int rmn = exp;
  while (rem != 1) {
    std::uint64_t squ = rem;
    int shf = 0;
    while (squ != 1 && shf < rmn) {
      squ = multiply_mod(squ, squ, mod);
      shf++;
    }
    assert(shf < rmn);
    std::uint64_t mul =
        power_mod(ste, std::uint64_t(1) << (rmn - shf - 1), mod);
    rt = multiply_mod(rt, mul, mod);
    ste = multiply_mod(mul, mul, mod);
    rem = multiply_mod(rem, ste, mod);
    rmn = shf;
  }
  return std::min(rt, mod - rt);
}

} // namespace noya

namespace noya {

/// @brief Decide and construct n = a^2 + b^2 for an unsigned 64-bit integer;
/// also count ordered signed representations using the two-square theorem.
namespace sum_two_squares_internal {

using u64 = std::uint64_t;
using u128 = unsigned __int128;
using i128 = __int128;

inline u64 integer_sqrt(u64 val) {
  u64 rt = u64(std::sqrt(static_cast<long double>(val)));
  while (u128(rt + 1) * (rt + 1) <= val) {
    rt++;
  }
  while (u128(rt) * rt > val) {
    rt--;
  }
  return rt;
}

inline std::pair<u64, u64> multiply(std::pair<u64, u64> lhs,
                                    std::pair<u64, u64> rhs) {
  i128 re = i128(lhs.first) * rhs.first - i128(lhs.second) * rhs.second;
  i128 im = i128(lhs.first) * rhs.second + i128(lhs.second) * rhs.first;
  u64 a = u64(re < 0 ? -re : re);
  u64 b = u64(im < 0 ? -im : im);
  if (a > b) {
    std::swap(a, b);
  }
  return {a, b};
}

inline std::optional<std::pair<u64, u64>> prime_representation(u64 p) {
  assert(p % 4 == 1 && is_prime(p));
  auto sqr = mod_sqrt(p - 1, p);
  assert(sqr.has_value());
  for (u64 rt : {*sqr, p - *sqr}) {
    u64 pre = p;
    u64 cur = rt;
    while (u128(cur) * cur > p) {
      u64 nxt = pre % cur;
      pre = cur;
      cur = nxt;
    }
    u64 sq = p - cur * cur;
    u64 oth = integer_sqrt(sq);
    if (oth * oth == sq) {
      return std::pair<u64, u64>{std::min(cur, oth), std::max(cur, oth)};
    }
  }
  return std::nullopt;
}

inline std::pair<u64, u64> power(std::pair<u64, u64> bas, int exp) {
  std::pair<u64, u64> res{1, 0};
  while (exp > 0) {
    if (exp & 1) {
      res = multiply(res, bas);
    }
    exp >>= 1;
    if (exp > 0) {
      bas = multiply(bas, bas);
    }
  }
  return res;
}

using gaussian = std::pair<i128, i128>;

inline gaussian signed_multiply(gaussian lhs, gaussian rhs) {
  return {lhs.first * rhs.first - lhs.second * rhs.second,
          lhs.first * rhs.second + lhs.second * rhs.first};
}

inline gaussian signed_power(gaussian bas, int exp) {
  gaussian res{1, 0};
  while (exp > 0) {
    if (exp & 1) {
      res = signed_multiply(res, bas);
    }
    exp >>= 1;
    if (exp > 0) {
      bas = signed_multiply(bas, bas);
    }
  }
  return res;
}

} // namespace sum_two_squares_internal

inline bool is_sum_two_squares(std::uint64_t n) {
  if (n == 0) {
    return true;
  }
  for (auto [p, exp] : factorize(n)) {
    if (p % 4 == 3 && exp % 2 == 1) {
      return false;
    }
  }
  return true;
}

/// @brief Return one pair 0 <= a <= b with a^2 + b^2 = n, or nullopt.
inline std::optional<std::pair<std::uint64_t, std::uint64_t>>
sum_two_squares(std::uint64_t n) {
  using namespace sum_two_squares_internal;
  if (n == 0) {
    return std::pair<u64, u64>{0, 0};
  }
  std::pair<u64, u64> res{1, 0};
  for (auto [p, exp] : factorize(n)) {
    if (p == 2) {
      res = multiply(res, power({1, 1}, exp));
    } else if (p % 4 == 1) {
      auto rep = prime_representation(p);
      assert(rep.has_value());
      res = multiply(res, power(*rep, exp));
    } else {
      if (exp % 2 == 1) {
        return std::nullopt;
      }
      u64 scl = 1;
      for (int i = 0; i < exp / 2; i++) {
        scl *= p;
      }
      res = multiply(res, {scl, 0});
    }
  }
  if (res.first > res.second) {
    std::swap(res.first, res.second);
  }
  return res;
}

/// @brief Count integer pairs (a,b), including signs and order, satisfying
/// a^2 + b^2 = n.
inline unsigned __int128 sum_two_squares_representation_count(std::uint64_t n) {
  if (n == 0) {
    return 1;
  }
  unsigned __int128 res = 4;
  for (auto [p, exp] : factorize(n)) {
    if (p % 4 == 3 && exp % 2 == 1) {
      return 0;
    }
    if (p % 4 == 1) {
      res *= exp + 1;
    }
  }
  return res;
}

/// @brief Return every ordered non-negative pair (a,b) with a^2 + b^2 = n.
/// The result is sorted and contains no duplicates. Requires n <= 1e18.
inline std::vector<std::pair<std::uint64_t, std::uint64_t>>
all_sum_two_squares(std::uint64_t n) {
  using namespace sum_two_squares_internal;
  assert(n <= 1000000000000000000ULL);
  if (n == 0) {
    return {{0, 0}};
  }

  auto fs = factorize(n);
  for (auto [p, exp] : fs) {
    if (p % 4 == 3 && exp % 2 == 1) {
      return {};
    }
  }

  std::vector<gaussian> rs{{1, 0}};
  for (auto [p, exp] : fs) {
    if (p % 4 == 3) {
      i128 scl = 1;
      for (int i = 0; i < exp / 2; i++) {
        scl *= p;
      }
      for (auto &[re, im] : rs) {
        re *= scl;
        im *= scl;
      }
      continue;
    }

    gaussian pf;
    if (p == 2) {
      pf = {1, 1};
      gaussian mul = signed_power(pf, exp);
      for (auto &val : rs) {
        val = signed_multiply(val, mul);
      }
      continue;
    }

    auto rep = prime_representation(p);
    assert(rep.has_value());
    pf = {rep->first, rep->second};
    std::vector<gaussian> pw(exp + 1, {1, 0});
    for (int i = 0; i < exp; i++) {
      pw[i + 1] = signed_multiply(pw[i], pf);
    }

    std::vector<gaussian> nxt;
    nxt.reserve(rs.size() * (exp + 1));
    for (gaussian cur : rs) {
      for (int cho = 0; cho <= exp; cho++) {
        gaussian cjg = pw[exp - cho];
        cjg.second = -cjg.second;
        gaussian fct = signed_multiply(pw[cho], cjg);
        nxt.push_back(signed_multiply(cur, fct));
      }
    }
    rs.swap(nxt);
  }

  std::vector<std::pair<u64, u64>> res;
  for (auto [re, im] : rs) {
    while (re <= 0 || im < 0) {
      i128 ore = re;
      re = -im;
      im = ore;
    }
    res.emplace_back(u64(re), u64(im));
    if (im == 0) {
      res.emplace_back(0, u64(re));
    }
  }
  std::sort(res.begin(), res.end());
  res.erase(std::unique(res.begin(), res.end()), res.end());
  return res;
}

} // namespace noya