Skip to content

discrete_log.hpp

SECTIONMath INCLUDEnoya/discrete_log.hpp

用 exBSGS 求最小非负 \(x\) 使 \(a^x\equiv b\pmod m\),允许底数与模数不互质,并在无解时返回空值。

\[ \displaystyle a^x\equiv b\pmod m,\qquad x=\min\{t\ge 0:a^t\equiv b\pmod m\} \]

Complexity: Time: O(sqrt(m)) expected time. Space: O(sqrt(m)).

AC 记录:discrete_logarithm_mod

跳到代码 · GitHub ↗

Implementation

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

/// @complexity Time: O(sqrt(m)) expected time.
/// Space: O(sqrt(m)).

#include "noya/factorize.hpp"

#include <cassert>
#include <cmath>
#include <cstdint>
#include <numeric>
#include <optional>
#include <unordered_map>

namespace noya {
namespace discrete_log_internal {

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

inline u64 inverse_mod(u64 val, u64 mod) {
  assert(mod >= 2 && std::gcd(val, mod) == 1);
  i128 c0 = 1;
  i128 cf = 0;
  u64 r0 = val;
  u64 rem = mod;
  while (rem != 0) {
    u64 quo = r0 / rem;
    u64 r1 = r0 - quo * rem;
    r0 = rem;
    rem = r1;
    i128 c1 = c0 - i128(quo) * cf;
    c0 = cf;
    cf = c1;
  }
  c0 %= i128(mod);
  if (c0 < 0) {
    c0 += mod;
  }
  return u64(c0);
}

inline u64 ceil_sqrt(u64 val) {
  u64 res = u64(std::sqrt(static_cast<long double>(val)));
  while (res != 0 && res > val / res) {
    res--;
  }
  while (res == 0 || res <= val / res) {
    if (res * res == val) {
      return res;
    }
    res++;
  }
  return res;
}

inline std::optional<u64> coprime_bsgs(u64 bas, u64 tar, u64 mod) {
  using factorize_internal::multiply_mod;
  using factorize_internal::power_mod;
  assert(mod >= 2 && std::gcd(bas, mod) == 1);
  u64 blk = ceil_sqrt(mod);
  std::unordered_map<u64, u64> bs;
  bs.reserve(std::size_t(blk * 2 + 1));
  u64 val = 1;
  for (u64 exp = 0; exp < blk; exp++) {
    bs.emplace(val, exp);
    val = multiply_mod(val, bas, mod);
  }
  u64 ib = power_mod(inverse_mod(bas, mod), blk, mod);
  val = tar;
  for (u64 gia = 0; gia <= blk; gia++) {
    auto it = bs.find(val);
    if (it != bs.end()) {
      return gia * blk + it->second;
    }
    val = multiply_mod(val, ib, mod);
  }
  return std::nullopt;
}

} // namespace discrete_log_internal

/// @brief Find the smallest x >= 0 satisfying bas^x = tar (mod mod),
/// or nullopt if no solution exists.
inline std::optional<std::uint64_t>
discrete_log(std::uint64_t bas, std::uint64_t tar, std::uint64_t mod) {
  using discrete_log_internal::coprime_bsgs;
  using factorize_internal::multiply_mod;
  assert(mod >= 1);
  if (mod == 1) {
    return 0;
  }
  bas %= mod;
  tar %= mod;
  if (tar == 1) {
    return 0;
  }

  std::uint64_t rmv = 0;
  std::uint64_t acc = 1;
  while (true) {
    std::uint64_t div = std::gcd(bas, mod);
    if (div == 1) {
      break;
    }
    if (tar == acc) {
      return rmv;
    }
    if (tar % div != 0) {
      return std::nullopt;
    }
    tar /= div;
    mod /= div;
    acc = multiply_mod(acc, bas / div, mod);
    rmv++;
  }
  if (tar == acc) {
    return rmv;
  }
  std::uint64_t nrm =
      multiply_mod(tar, discrete_log_internal::inverse_mod(acc, mod), mod);
  auto res = coprime_bsgs(bas % mod, nrm, mod);
  if (!res) {
    return std::nullopt;
  }
  return *res + rmv;
}

/// @brief Extended BSGS: find the smallest x >= 0 satisfying
/// bas^x = tar (mod mod), without requiring gcd(bas, mod) = 1.
inline std::optional<std::uint64_t> exbsgs(std::uint64_t bas, std::uint64_t tar,
                                           std::uint64_t mod) {
  return discrete_log(bas, tar, mod);
}

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

/// @complexity Time: O(sqrt(m)) expected time.
/// Space: O(sqrt(m)).

#include "noya/factorize.hpp"

#include <cassert>
#include <cmath>
#include <cstdint>
#include <numeric>
#include <optional>
#include <unordered_map>

namespace noya {
namespace discrete_log_internal {

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

inline u64 inverse_mod(u64 val, u64 mod) {
  assert(mod >= 2 && std::gcd(val, mod) == 1);
  i128 c0 = 1;
  i128 cf = 0;
  u64 r0 = val;
  u64 rem = mod;
  while (rem != 0) {
    u64 quo = r0 / rem;
    u64 r1 = r0 - quo * rem;
    r0 = rem;
    rem = r1;
    i128 c1 = c0 - i128(quo) * cf;
    c0 = cf;
    cf = c1;
  }
  c0 %= i128(mod);
  if (c0 < 0) {
    c0 += mod;
  }
  return u64(c0);
}

inline u64 ceil_sqrt(u64 val) {
  u64 res = u64(std::sqrt(static_cast<long double>(val)));
  while (res != 0 && res > val / res) {
    res--;
  }
  while (res == 0 || res <= val / res) {
    if (res * res == val) {
      return res;
    }
    res++;
  }
  return res;
}

inline std::optional<u64> coprime_bsgs(u64 bas, u64 tar, u64 mod) {
  using factorize_internal::multiply_mod;
  using factorize_internal::power_mod;
  assert(mod >= 2 && std::gcd(bas, mod) == 1);
  u64 blk = ceil_sqrt(mod);
  std::unordered_map<u64, u64> bs;
  bs.reserve(std::size_t(blk * 2 + 1));
  u64 val = 1;
  for (u64 exp = 0; exp < blk; exp++) {
    bs.emplace(val, exp);
    val = multiply_mod(val, bas, mod);
  }
  u64 ib = power_mod(inverse_mod(bas, mod), blk, mod);
  val = tar;
  for (u64 gia = 0; gia <= blk; gia++) {
    auto it = bs.find(val);
    if (it != bs.end()) {
      return gia * blk + it->second;
    }
    val = multiply_mod(val, ib, mod);
  }
  return std::nullopt;
}

} // namespace discrete_log_internal

/// @brief Find the smallest x >= 0 satisfying bas^x = tar (mod mod),
/// or nullopt if no solution exists.
inline std::optional<std::uint64_t>
discrete_log(std::uint64_t bas, std::uint64_t tar, std::uint64_t mod) {
  using discrete_log_internal::coprime_bsgs;
  using factorize_internal::multiply_mod;
  assert(mod >= 1);
  if (mod == 1) {
    return 0;
  }
  bas %= mod;
  tar %= mod;
  if (tar == 1) {
    return 0;
  }

  std::uint64_t rmv = 0;
  std::uint64_t acc = 1;
  while (true) {
    std::uint64_t div = std::gcd(bas, mod);
    if (div == 1) {
      break;
    }
    if (tar == acc) {
      return rmv;
    }
    if (tar % div != 0) {
      return std::nullopt;
    }
    tar /= div;
    mod /= div;
    acc = multiply_mod(acc, bas / div, mod);
    rmv++;
  }
  if (tar == acc) {
    return rmv;
  }
  std::uint64_t nrm =
      multiply_mod(tar, discrete_log_internal::inverse_mod(acc, mod), mod);
  auto res = coprime_bsgs(bas % mod, nrm, mod);
  if (!res) {
    return std::nullopt;
  }
  return *res + rmv;
}

/// @brief Extended BSGS: find the smallest x >= 0 satisfying
/// bas^x = tar (mod mod), without requiring gcd(bas, mod) = 1.
inline std::optional<std::uint64_t> exbsgs(std::uint64_t bas, std::uint64_t tar,
                                           std::uint64_t mod) {
  return discrete_log(bas, tar, mod);
}

} // namespace noya

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

/// @complexity Time: O(sqrt(m)) expected time.
/// Space: O(sqrt(m)).

/// @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

namespace noya {
namespace discrete_log_internal {

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

inline u64 inverse_mod(u64 val, u64 mod) {
  assert(mod >= 2 && std::gcd(val, mod) == 1);
  i128 c0 = 1;
  i128 cf = 0;
  u64 r0 = val;
  u64 rem = mod;
  while (rem != 0) {
    u64 quo = r0 / rem;
    u64 r1 = r0 - quo * rem;
    r0 = rem;
    rem = r1;
    i128 c1 = c0 - i128(quo) * cf;
    c0 = cf;
    cf = c1;
  }
  c0 %= i128(mod);
  if (c0 < 0) {
    c0 += mod;
  }
  return u64(c0);
}

inline u64 ceil_sqrt(u64 val) {
  u64 res = u64(std::sqrt(static_cast<long double>(val)));
  while (res != 0 && res > val / res) {
    res--;
  }
  while (res == 0 || res <= val / res) {
    if (res * res == val) {
      return res;
    }
    res++;
  }
  return res;
}

inline std::optional<u64> coprime_bsgs(u64 bas, u64 tar, u64 mod) {
  using factorize_internal::multiply_mod;
  using factorize_internal::power_mod;
  assert(mod >= 2 && std::gcd(bas, mod) == 1);
  u64 blk = ceil_sqrt(mod);
  std::unordered_map<u64, u64> bs;
  bs.reserve(std::size_t(blk * 2 + 1));
  u64 val = 1;
  for (u64 exp = 0; exp < blk; exp++) {
    bs.emplace(val, exp);
    val = multiply_mod(val, bas, mod);
  }
  u64 ib = power_mod(inverse_mod(bas, mod), blk, mod);
  val = tar;
  for (u64 gia = 0; gia <= blk; gia++) {
    auto it = bs.find(val);
    if (it != bs.end()) {
      return gia * blk + it->second;
    }
    val = multiply_mod(val, ib, mod);
  }
  return std::nullopt;
}

} // namespace discrete_log_internal

/// @brief Find the smallest x >= 0 satisfying bas^x = tar (mod mod),
/// or nullopt if no solution exists.
inline std::optional<std::uint64_t>
discrete_log(std::uint64_t bas, std::uint64_t tar, std::uint64_t mod) {
  using discrete_log_internal::coprime_bsgs;
  using factorize_internal::multiply_mod;
  assert(mod >= 1);
  if (mod == 1) {
    return 0;
  }
  bas %= mod;
  tar %= mod;
  if (tar == 1) {
    return 0;
  }

  std::uint64_t rmv = 0;
  std::uint64_t acc = 1;
  while (true) {
    std::uint64_t div = std::gcd(bas, mod);
    if (div == 1) {
      break;
    }
    if (tar == acc) {
      return rmv;
    }
    if (tar % div != 0) {
      return std::nullopt;
    }
    tar /= div;
    mod /= div;
    acc = multiply_mod(acc, bas / div, mod);
    rmv++;
  }
  if (tar == acc) {
    return rmv;
  }
  std::uint64_t nrm =
      multiply_mod(tar, discrete_log_internal::inverse_mod(acc, mod), mod);
  auto res = coprime_bsgs(bas % mod, nrm, mod);
  if (!res) {
    return std::nullopt;
  }
  return *res + rmv;
}

/// @brief Extended BSGS: find the smallest x >= 0 satisfying
/// bas^x = tar (mod mod), without requiring gcd(bas, mod) = 1.
inline std::optional<std::uint64_t> exbsgs(std::uint64_t bas, std::uint64_t tar,
                                           std::uint64_t mod) {
  return discrete_log(bas, tar, mod);
}

} // namespace noya