q_binomial_prime.hpp¶
在素数模下计算高斯二项式(q-组合数),并用 q-Lucas 处理大参数。
\[
\displaystyle \binom{n}{k}_q
\]
Complexity: Time: expected factorization time for p-1 plus O(max(n/order) + min(order,max n) + T) for T queries. Space: O(max(n/order) + min(order,max n)).
AC 记录:q_binomial_coefficient_prime_mod。
Implementation¶
当前头文件,省略 include guard;依赖见 #include。
/// @complexity Time: expected factorization time for p-1 plus
/// O(max(n/order) + min(order,max n) + T) for T queries.
/// Space: O(max(n/order) + min(order,max n)).
#include "noya/factorize.hpp"
#include "noya/prime_binomial_table.hpp"
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <utility>
#include <vector>
namespace noya {
/// @brief Answer Gaussian binomial coefficients at a fixed q modulo a prime.
/// If d is the multiplicative order of q, q-Lucas gives
/// [n choose k]_q = C(floor(n/d),floor(k/d)) [n mod d choose k mod d]_q.
/// Ordinary factorials handle the first factor, while products of 1-q^i for
/// i<d handle the second. q=0 and q=1 are treated by their direct limits.
class q_binomial_prime_table {
public:
q_binomial_prime_table() = default;
q_binomial_prime_table(const std::vector<std::pair<int, int>> &que,
std::uint32_t p, std::uint32_t q) {
build(que, p, q);
}
void build(const std::vector<std::pair<int, int>> &que, std::uint32_t p,
std::uint32_t q) {
md = p;
q_ = q % p;
int mx = 0;
for (auto [n, k] : que) {
assert(n >= 0 && k >= 0 && std::uint64_t(n) < p && std::uint64_t(k) < p);
mx = std::max(mx, n);
}
if (q_ == 0) {
od_ = 0;
return;
}
od_ = p - 1;
for (auto [fct, exp] : factorize(od_)) {
(void)exp;
while (od_ % fct == 0 && power(q_, od_ / fct) == 1) {
od_ /= std::uint32_t(fct);
}
}
ord.build(mx / int(od_), p);
int lim = std::min<std::uint64_t>(mx, od_ - 1);
fac.resize(lim + 1);
ifc.resize(lim + 1);
fac[0] = 1;
std::uint32_t qp = 1;
for (int i = 1; i <= lim; i++) {
qp = multiply(qp, q_);
std::uint32_t fct = qp == 0 ? 1 : md + 1ULL - qp;
fct %= md;
assert(fct != 0);
fac[i] = multiply(fac[i - 1], fct);
}
ifc[lim] = power(fac[lim], p - 2);
qp = power(q_, lim);
std::uint32_t iq = power(q_, p - 2);
for (int i = lim; i > 0; i--) {
std::uint32_t fct = std::uint32_t((std::uint64_t(md) + 1 - qp) % md);
ifc[i - 1] = multiply(ifc[i], fct);
qp = multiply(qp, iq);
}
}
std::uint32_t choose(int n, int k) const {
if (k < 0 || k > n) {
return 0;
}
if (q_ == 0) {
return 1 % md;
}
int hn = n / int(od_);
int hk = k / int(od_);
int ln = n % int(od_);
int lk = k % int(od_);
if (lk > ln) {
return 0;
}
std::uint32_t low = multiply(fac[ln], multiply(ifc[lk], ifc[ln - lk]));
return multiply(ord.choose(hn, hk), low);
}
std::uint32_t order() const { return od_; }
private:
std::uint32_t md = 1;
std::uint32_t q_ = 0;
std::uint32_t od_ = 0;
prime_binomial_table ord;
std::vector<std::uint32_t> fac;
std::vector<std::uint32_t> ifc;
std::uint32_t multiply(std::uint64_t a, std::uint64_t b) const {
return std::uint32_t(a * b % md);
}
std::uint32_t power(std::uint32_t val, std::uint64_t exp) const {
std::uint32_t res = 1 % md;
while (exp > 0) {
if (exp & 1) {
res = multiply(res, val);
}
val = multiply(val, val);
exp >>= 1;
}
return res;
}
};
} // namespace noya
#ifndef NOYA_Q_BINOMIAL_PRIME_HPP
#define NOYA_Q_BINOMIAL_PRIME_HPP 1
/// @complexity Time: expected factorization time for p-1 plus
/// O(max(n/order) + min(order,max n) + T) for T queries.
/// Space: O(max(n/order) + min(order,max n)).
#include "noya/factorize.hpp"
#include "noya/prime_binomial_table.hpp"
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <utility>
#include <vector>
namespace noya {
/// @brief Answer Gaussian binomial coefficients at a fixed q modulo a prime.
/// If d is the multiplicative order of q, q-Lucas gives
/// [n choose k]_q = C(floor(n/d),floor(k/d)) [n mod d choose k mod d]_q.
/// Ordinary factorials handle the first factor, while products of 1-q^i for
/// i<d handle the second. q=0 and q=1 are treated by their direct limits.
class q_binomial_prime_table {
public:
q_binomial_prime_table() = default;
q_binomial_prime_table(const std::vector<std::pair<int, int>> &que,
std::uint32_t p, std::uint32_t q) {
build(que, p, q);
}
void build(const std::vector<std::pair<int, int>> &que, std::uint32_t p,
std::uint32_t q) {
md = p;
q_ = q % p;
int mx = 0;
for (auto [n, k] : que) {
assert(n >= 0 && k >= 0 && std::uint64_t(n) < p && std::uint64_t(k) < p);
mx = std::max(mx, n);
}
if (q_ == 0) {
od_ = 0;
return;
}
od_ = p - 1;
for (auto [fct, exp] : factorize(od_)) {
(void)exp;
while (od_ % fct == 0 && power(q_, od_ / fct) == 1) {
od_ /= std::uint32_t(fct);
}
}
ord.build(mx / int(od_), p);
int lim = std::min<std::uint64_t>(mx, od_ - 1);
fac.resize(lim + 1);
ifc.resize(lim + 1);
fac[0] = 1;
std::uint32_t qp = 1;
for (int i = 1; i <= lim; i++) {
qp = multiply(qp, q_);
std::uint32_t fct = qp == 0 ? 1 : md + 1ULL - qp;
fct %= md;
assert(fct != 0);
fac[i] = multiply(fac[i - 1], fct);
}
ifc[lim] = power(fac[lim], p - 2);
qp = power(q_, lim);
std::uint32_t iq = power(q_, p - 2);
for (int i = lim; i > 0; i--) {
std::uint32_t fct = std::uint32_t((std::uint64_t(md) + 1 - qp) % md);
ifc[i - 1] = multiply(ifc[i], fct);
qp = multiply(qp, iq);
}
}
std::uint32_t choose(int n, int k) const {
if (k < 0 || k > n) {
return 0;
}
if (q_ == 0) {
return 1 % md;
}
int hn = n / int(od_);
int hk = k / int(od_);
int ln = n % int(od_);
int lk = k % int(od_);
if (lk > ln) {
return 0;
}
std::uint32_t low = multiply(fac[ln], multiply(ifc[lk], ifc[ln - lk]));
return multiply(ord.choose(hn, hk), low);
}
std::uint32_t order() const { return od_; }
private:
std::uint32_t md = 1;
std::uint32_t q_ = 0;
std::uint32_t od_ = 0;
prime_binomial_table ord;
std::vector<std::uint32_t> fac;
std::vector<std::uint32_t> ifc;
std::uint32_t multiply(std::uint64_t a, std::uint64_t b) const {
return std::uint32_t(a * b % md);
}
std::uint32_t power(std::uint32_t val, std::uint64_t exp) const {
std::uint32_t res = 1 % md;
while (exp > 0) {
if (exp & 1) {
res = multiply(res, val);
}
val = multiply(val, val);
exp >>= 1;
}
return res;
}
};
} // namespace noya
#endif // NOYA_Q_BINOMIAL_PRIME_HPP
#include <algorithm>
#include <array>
#include <cassert>
#include <cstdint>
#include <numeric>
#include <utility>
#include <vector>
/// @complexity Time: expected factorization time for p-1 plus
/// O(max(n/order) + min(order,max n) + T) for T queries.
/// Space: O(max(n/order) + min(order,max 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(N + log p) preprocessing and O(1) per query.
/// Space: O(N).
namespace noya {
/// @brief Binomial coefficients modulo a runtime prime p for arguments below
/// p. Store factorials and inverse factorials through N; Fermat inversion of
/// N! followed by a backward sweep obtains every inverse using one power.
class prime_binomial_table {
public:
prime_binomial_table() = default;
prime_binomial_table(int mx, std::uint32_t p) { build(mx, p); }
void build(int mx, std::uint32_t p) {
assert(mx >= 0 && std::uint64_t(mx) < p);
md = p;
fac.resize(mx + 1);
ifc.resize(mx + 1);
fac[0] = 1 % p;
for (int i = 1; i <= mx; i++) {
fac[i] = multiply(fac[i - 1], i);
}
ifc[mx] = power(fac[mx], p - 2);
for (int i = mx; i > 0; i--) {
ifc[i - 1] = multiply(ifc[i], i);
}
}
std::uint32_t choose(int n, int k) const {
if (k < 0 || k > n) {
return 0;
}
assert(n < int(fac.size()));
return multiply(fac[n], multiply(ifc[k], ifc[n - k]));
}
private:
std::uint32_t md = 1;
std::vector<std::uint32_t> fac;
std::vector<std::uint32_t> ifc;
std::uint32_t multiply(std::uint64_t a, std::uint64_t b) const {
return std::uint32_t(a * b % md);
}
std::uint32_t power(std::uint32_t val, std::uint64_t exp) const {
std::uint32_t res = 1 % md;
while (exp > 0) {
if (exp & 1) {
res = multiply(res, val);
}
val = multiply(val, val);
exp >>= 1;
}
return res;
}
};
} // namespace noya
namespace noya {
/// @brief Answer Gaussian binomial coefficients at a fixed q modulo a prime.
/// If d is the multiplicative order of q, q-Lucas gives
/// [n choose k]_q = C(floor(n/d),floor(k/d)) [n mod d choose k mod d]_q.
/// Ordinary factorials handle the first factor, while products of 1-q^i for
/// i<d handle the second. q=0 and q=1 are treated by their direct limits.
class q_binomial_prime_table {
public:
q_binomial_prime_table() = default;
q_binomial_prime_table(const std::vector<std::pair<int, int>> &que,
std::uint32_t p, std::uint32_t q) {
build(que, p, q);
}
void build(const std::vector<std::pair<int, int>> &que, std::uint32_t p,
std::uint32_t q) {
md = p;
q_ = q % p;
int mx = 0;
for (auto [n, k] : que) {
assert(n >= 0 && k >= 0 && std::uint64_t(n) < p && std::uint64_t(k) < p);
mx = std::max(mx, n);
}
if (q_ == 0) {
od_ = 0;
return;
}
od_ = p - 1;
for (auto [fct, exp] : factorize(od_)) {
(void)exp;
while (od_ % fct == 0 && power(q_, od_ / fct) == 1) {
od_ /= std::uint32_t(fct);
}
}
ord.build(mx / int(od_), p);
int lim = std::min<std::uint64_t>(mx, od_ - 1);
fac.resize(lim + 1);
ifc.resize(lim + 1);
fac[0] = 1;
std::uint32_t qp = 1;
for (int i = 1; i <= lim; i++) {
qp = multiply(qp, q_);
std::uint32_t fct = qp == 0 ? 1 : md + 1ULL - qp;
fct %= md;
assert(fct != 0);
fac[i] = multiply(fac[i - 1], fct);
}
ifc[lim] = power(fac[lim], p - 2);
qp = power(q_, lim);
std::uint32_t iq = power(q_, p - 2);
for (int i = lim; i > 0; i--) {
std::uint32_t fct = std::uint32_t((std::uint64_t(md) + 1 - qp) % md);
ifc[i - 1] = multiply(ifc[i], fct);
qp = multiply(qp, iq);
}
}
std::uint32_t choose(int n, int k) const {
if (k < 0 || k > n) {
return 0;
}
if (q_ == 0) {
return 1 % md;
}
int hn = n / int(od_);
int hk = k / int(od_);
int ln = n % int(od_);
int lk = k % int(od_);
if (lk > ln) {
return 0;
}
std::uint32_t low = multiply(fac[ln], multiply(ifc[lk], ifc[ln - lk]));
return multiply(ord.choose(hn, hk), low);
}
std::uint32_t order() const { return od_; }
private:
std::uint32_t md = 1;
std::uint32_t q_ = 0;
std::uint32_t od_ = 0;
prime_binomial_table ord;
std::vector<std::uint32_t> fac;
std::vector<std::uint32_t> ifc;
std::uint32_t multiply(std::uint64_t a, std::uint64_t b) const {
return std::uint32_t(a * b % md);
}
std::uint32_t power(std::uint32_t val, std::uint64_t exp) const {
std::uint32_t res = 1 % md;
while (exp > 0) {
if (exp & 1) {
res = multiply(res, val);
}
val = multiply(val, val);
exp >>= 1;
}
return res;
}
};
} // namespace noya