素数法上で x の平方根を 1 つ返す
lib/number_theory/root_mod.hpp
インクルード
#include "number_theory/root_mod.hpp"
関数
| 名前 | 説明 |
|---|---|
sqrt_mod |
素数法上で x の平方根を 1 つ返す |
kth_root_mod |
$K$ 乗根 $\bmod P$($P$ は素数)を 1 つ求める |
検証
- test/yosupo/math/kth_root_mod.test.cpp
- test/yosupo/math/sqrt_mod.test.cpp
- test/yukicoder/0551.test.cpp
依存するヘッダ(3)
-
internal/internal_math.hpp
lib/internal/internal_math.hpp -
internal/internal_type_traits.hpp
lib/internal/internal_type_traits.hpp -
modint (static_modint / dynamic_modint)
lib/number_theory/modint.hpp
このヘッダを使うライブラリ(4)
-
ベル数を $B_0$ から $B_n$ まで列挙する
lib/combinatorics/bell.hpp -
分割数を $p(0)$ から $p(n)$ まで列挙する
lib/combinatorics/partition_function.hpp -
第1種スターリング数(符号付き)s(n, 0), s(n, 1), ..., s(n, n) を求める(行)
lib/combinatorics/stirling.hpp -
形式的冪級数 (FPS)
lib/convolution/formal_power_series.hpp
検証プログラム(24)
-
test/yosupo/enumerative_conbinatorics/bell_number.test.cpp
test/yosupo/enumerative_conbinatorics/bell_number.test.cpp -
test/yosupo/enumerative_conbinatorics/partition_function.test.cpp
test/yosupo/enumerative_conbinatorics/partition_function.test.cpp -
test/yosupo/enumerative_conbinatorics/stirling_number_of_the_first_kind.test.cpp
test/yosupo/enumerative_conbinatorics/stirling_number_of_the_first_kind.test.cpp -
test/yosupo/enumerative_conbinatorics/stirling_number_of_the_first_kind_fixed_k.test.cpp
test/yosupo/enumerative_conbinatorics/stirling_number_of_the_first_kind_fixed_k.test.cpp -
test/yosupo/enumerative_conbinatorics/stirling_number_of_the_second_kind.test.cpp
test/yosupo/enumerative_conbinatorics/stirling_number_of_the_second_kind.test.cpp -
test/yosupo/enumerative_conbinatorics/stirling_number_of_the_second_kind_fixed_k.test.cpp
test/yosupo/enumerative_conbinatorics/stirling_number_of_the_second_kind_fixed_k.test.cpp -
test/yosupo/math/kth_root_mod.test.cpp
test/yosupo/math/kth_root_mod.test.cpp -
test/yosupo/math/sqrt_mod.test.cpp
test/yosupo/math/sqrt_mod.test.cpp -
test/yosupo/polynomial/composition_of_formal_power_series.test.cpp
test/yosupo/polynomial/composition_of_formal_power_series.test.cpp -
test/yosupo/polynomial/composition_of_formal_power_series_large.test.cpp
test/yosupo/polynomial/composition_of_formal_power_series_large.test.cpp -
test/yosupo/polynomial/compositional_inverse_of_formal_power_series.test.cpp
test/yosupo/polynomial/compositional_inverse_of_formal_power_series.test.cpp -
test/yosupo/polynomial/compositional_inverse_of_formal_power_series_large.test.cpp
test/yosupo/polynomial/compositional_inverse_of_formal_power_series_large.test.cpp -
test/yosupo/polynomial/division_of_polynomials.test.cpp
test/yosupo/polynomial/division_of_polynomials.test.cpp -
test/yosupo/polynomial/exp_of_formal_power_series.test.cpp
test/yosupo/polynomial/exp_of_formal_power_series.test.cpp -
test/yosupo/polynomial/inv_of_formal_power_series.test.cpp
test/yosupo/polynomial/inv_of_formal_power_series.test.cpp -
test/yosupo/polynomial/inv_of_polynomials.test.cpp
test/yosupo/polynomial/inv_of_polynomials.test.cpp -
test/yosupo/polynomial/log_of_formal_power_series.test.cpp
test/yosupo/polynomial/log_of_formal_power_series.test.cpp -
test/yosupo/polynomial/multipoint_evaluation.test.cpp
test/yosupo/polynomial/multipoint_evaluation.test.cpp -
test/yosupo/polynomial/polynomial_interpolation.test.cpp
test/yosupo/polynomial/polynomial_interpolation.test.cpp -
test/yosupo/polynomial/polynomial_taylor_shift.test.cpp
test/yosupo/polynomial/polynomial_taylor_shift.test.cpp -
test/yosupo/polynomial/pow_of_formal_power_series.test.cpp
test/yosupo/polynomial/pow_of_formal_power_series.test.cpp -
test/yosupo/polynomial/shift_of_sampling_points_of_polynomial.test.cpp
test/yosupo/polynomial/shift_of_sampling_points_of_polynomial.test.cpp -
test/yosupo/polynomial/sqrt_of_formal_power_series.test.cpp
test/yosupo/polynomial/sqrt_of_formal_power_series.test.cpp -
test/yukicoder/0551.test.cpp
test/yukicoder/0551.test.cpp
実装
#pragma once
#include <cassert>
#include <cmath>
#include <cstdint>
#include <map>
#include <numeric>
#include <optional>
#include "internal/internal_math.hpp"
#include "number_theory/modint.hpp"
namespace internal {
template <internal::modint mint>
bool has_sqrt_mod(mint x) {
return x == 0 || x.pow(mint::mod() / 2) == 1;
}
// Adleman-Manders-Miller: mod - 1 = p^s * q (p ∤ q) の下で a の p^e 乗根を1つ求める
// (e <= s、a は p^e 乗剰余であることが前提)。Tonelli-Shanks を p^e 乗根へ一般化したもの。
template <internal::modint mint>
mint peth_root(mint a, std::int64_t p, int e) {
const std::int64_t mod = mint::mod();
std::int64_t q = mod - 1;
int s = 0;
while (q % p == 0) q /= p, ++s;
std::int64_t pe = internal::pow_mod_constexpr(p, e, mod); // p^e (< mod なので厳密値)
// a^t が a の p^e 乗根の候補になるよう t = ((p^e - 1) q^{-1 mod p^e} q + 1) / p^e を選ぶ
std::int64_t t = ((pe - 1) * internal::inv_gcd(q, pe).second % pe * q + 1) / pe;
mint ans = a.pow(t);
mint c(2);
while (c.pow((mod - 1) / p) == 1) ++c; // p 乗非剰余
c = c.pow(q);
// BSGS で p^{s-1} 乗根の離散対数を引き、Hensel 持ち上げで e から s まで精度を上げる
std::int64_t pow_p_s1 = internal::pow_mod_constexpr(p, s - 1, mod - 1);
int v = (int)std::sqrt((double)(s - e) * (double)p) + 1;
mint mul = c.pow((std::int64_t)v * pow_p_s1 % (mod - 1));
mint inv_mul = c.pow(pow_p_s1).inv();
std::map<std::uint32_t, int> table;
mint add(1);
for (int i = 0; i <= v; ++i) {
table[add.val()] = i;
add *= mul;
}
for (int i = e; i < s; ++i) {
mint err = ans.pow(pe).inv() * a;
mint target = err.pow(internal::pow_mod_constexpr(p, s - 1 - i, mod - 1));
for (int j = 0;; ++j) {
auto it = table.find(target.val());
if (it != table.end()) {
std::int64_t exponent =
(std::int64_t)(j + v * it->second) * internal::pow_mod_constexpr(p, i - e, mod - 1) % (mod - 1);
ans *= c.pow(exponent);
break;
}
target *= inv_mul;
}
}
return ans;
}
} // namespace internal
/// @brief 素数法上で `x` の平方根を 1 つ返す
/// @return 平方根が存在しなければ `std::nullopt`
/// @complexity 法を $P$ として $O(\log^2 P)$
template <internal::modint mint>
std::optional<mint> sqrt_mod(mint x) {
if (!internal::has_sqrt_mod(x)) return std::nullopt;
const int p = mint::mod();
if (x == 0 || x == 1) return x;
if (p % 4 == 3) return x.pow(p / 4 + 1);
int q = p - 1, s = 0;
while (~q & 1) q >>= 1, ++s;
mint z(1);
while (internal::has_sqrt_mod(z)) ++z;
mint c = z.pow(q);
mint t = x.pow(q);
mint r = x.pow(q / 2 + 1);
while (t != 1) {
int m = 0;
mint u = t;
while (u != 1) ++m, u *= u;
while (s != m) {
--s;
if (s == m) r *= c;
c *= c;
}
t *= c;
}
return r;
}
/// @brief $K$ 乗根 $\bmod P$($P$ は素数)を 1 つ求める
/// @details $x^K \equiv a \pmod P$ なる $x$ を Adleman-Manders-Miller アルゴリズムで求める。
/// $0^0 \equiv 1$ の規約に従う。$\gcd(K, P - 1)$ の素因数ごとに @ref internal::peth_root
/// を適用して合成する。
/// @tparam mint static modint
/// @param a 右辺
/// @param k 指数($0 \le k$)
/// @return 条件を満たす $x$ の 1 つ。解が存在しないときは `std::nullopt`
/// @complexity 法を $P$ として $O(\sqrt P\log^2 P)$
template <internal::modint mint>
std::optional<mint> kth_root_mod(mint a, std::int64_t k) {
assert(k >= 0);
const std::int64_t mod = mint::mod();
if (k > 0 && a == 0) return mint(0);
k %= mod - 1;
std::int64_t g = std::gcd(k, mod - 1);
if (a.pow((mod - 1) / g) != 1) return std::nullopt;
a = a.pow(internal::inv_gcd(k / g, (mod - 1) / g).second);
for (std::int64_t d = 2; d * d <= g; ++d) {
int sz = 0;
while (g % d == 0) g /= d, ++sz;
if (sz > 0) a = internal::peth_root(a, d, sz);
}
if (g > 1) a = internal::peth_root(a, g, 1);
return a;
}