mod sqrt(Tonelli-Shanks algorithm)
(modulo/mod-sqrt.hpp)
- View this file on GitHub
- Last update: 2023-05-29 20:16:02+09:00
- Include:
#include "modulo/mod-sqrt.hpp"
mod sqrt(Tonelli-Shanks algorithm)
整数$a$、素数$p$に対して$x^2 \equiv a \mod p$を満たす$x$を$\mathrm{O}(\log ^2 p)$で計算するライブラリ。
概要
37zigenさんの記事を大きく参考にした。
まず、オイラーの規準により
\[a^\frac{p-1}{2} \equiv 1 \mod p\]であることが$a$が平方剰余を持つ必要十分条件である。以降は$a$が平方剰余を持つ場合のみ考える。
$\mod p$上の原始根を$r$と置き、$x\equiv r^y,a\equiv r^b$とすると上の等式は
\[x^2 \equiv a \mod p \leftrightarrow 2y \equiv b \mod p-1\]と言い換えられる。よって原始根がわかれば解は求まるが、離散対数問題を解く部分が$\mathrm{O}(\sqrt{p})$程度の計算量となり遅い。そこで、原始根を計算せずにうまく$a$を利用して$x$を計算できるように工夫をする。(つまり、$\mod p-1$の世界での計算を$\mod p$の世界での計算にうまく置き換えて解く。)
$p - 1 =s\cdot 2^t$($t$は奇数)を満たすように$s,t$を取ると、中国剰余定理より
\[2y \equiv b \mod s \wedge 2y \equiv b \mod 2^t\]となる$y$を見つければ良いとわかる。ここで、$x,y$の初期値として
\[x_0=a^\frac{s+1}{2} \rightarrow y_0 = \frac{s+1}{2}b\mod p-1\]を取ると、$2y_0 \equiv b \mod s, 2y_0 \equiv (s+1)b \mod 2^t$となり、$\mod s$は求める$y$に一致するので、$sb$だけずれている$\mod t$の方をうまく微調整すればよいとわかる。
ここで、$u^\frac{p-1}{2} \not\equiv 1\mod p$である$u$を乱択で選ぶと、
\[\mathrm{Ind}_r u^s \equiv0 \mod s,\ \mathrm{Ind}_r u^s \equiv 1 \mod 2\]が従う。よって$x$に$u^s$を掛けると、$y \mod s$は変わらず$y \mod 2^t$の1ビット目が反転することになる。この$u^s$を利用して誤差を調整する。
まず、現在の$y$の誤差項$2y-b\mod 2^t$の最下位ビットは、現在の$a^{-1}x^2$を自乗していって$1$になるまでにかかった回数を$t’$としたときに$t-t’$ビット目になる。最下位ビットが分かれば、$x$に$\mathrm{pow}(u^s,2^{t-t’-1})$を掛けることで最下位ビットを$0$にすることが出来る。このアルゴリズムを用いて小さいビットから0にしていき、$a^{-1}x^2 \equiv 1$になるまで繰り返せば$x$を求めることが出来る。
計算量は、誤差調整の1回あたり$\mathrm{O}(\log p)$で最大$t=\mathrm{O}(\log p)$回かかるので全体で$\mathrm{O}(\log ^2 p)$となる。
Depends on
Required by
Verified with
Code
#pragma once
#include "../modint/arbitrary-montgomery-modint.hpp"
int64_t mod_sqrt(const int64_t &a, const int64_t &p) {
assert(0 <= a && a < p);
if (a < 2) return a;
using Mint = ArbitraryLazyMontgomeryModInt<409075245>;
Mint::set_mod(p);
if (Mint(a).pow((p - 1) >> 1) != 1) return -1;
Mint b = 1, one = 1;
while (b.pow((p - 1) >> 1) == 1) b += one;
int64_t m = p - 1, e = 0;
while (m % 2 == 0) m >>= 1, e += 1;
Mint x = Mint(a).pow((m - 1) >> 1);
Mint y = Mint(a) * x * x;
x *= a;
Mint z = Mint(b).pow(m);
while (y != 1) {
int64_t j = 0;
Mint t = y;
while (t != one) {
j += 1;
t *= t;
}
z = z.pow(int64_t(1) << (e - j - 1));
x *= z;
z *= z;
y *= z;
e = j;
}
return x.get();
}
/**
* @brief mod sqrt(Tonelli-Shanks algorithm)
* @docs docs/modulo/mod-sqrt.md
*/
#line 2 "modint/arbitrary-montgomery-modint.hpp"
#include <iostream>
using namespace std;
template <typename Int, typename UInt, typename Long, typename ULong, int id>
struct ArbitraryLazyMontgomeryModIntBase {
using mint = ArbitraryLazyMontgomeryModIntBase;
inline static UInt mod;
inline static UInt r;
inline static UInt n2;
static constexpr int bit_length = sizeof(UInt) * 8;
static UInt get_r() {
UInt ret = mod;
while (mod * ret != 1) ret *= UInt(2) - mod * ret;
return ret;
}
static void set_mod(UInt m) {
assert(m < (UInt(1u) << (bit_length - 2)));
assert((m & 1) == 1);
mod = m, n2 = -ULong(m) % m, r = get_r();
}
UInt a;
ArbitraryLazyMontgomeryModIntBase() : a(0) {}
ArbitraryLazyMontgomeryModIntBase(const Long &b)
: a(reduce(ULong(b % mod + mod) * n2)){};
static UInt reduce(const ULong &b) {
return (b + ULong(UInt(b) * UInt(-r)) * mod) >> bit_length;
}
mint &operator+=(const mint &b) {
if (Int(a += b.a - 2 * mod) < 0) a += 2 * mod;
return *this;
}
mint &operator-=(const mint &b) {
if (Int(a -= b.a) < 0) a += 2 * mod;
return *this;
}
mint &operator*=(const mint &b) {
a = reduce(ULong(a) * b.a);
return *this;
}
mint &operator/=(const mint &b) {
*this *= b.inverse();
return *this;
}
mint operator+(const mint &b) const { return mint(*this) += b; }
mint operator-(const mint &b) const { return mint(*this) -= b; }
mint operator*(const mint &b) const { return mint(*this) *= b; }
mint operator/(const mint &b) const { return mint(*this) /= b; }
bool operator==(const mint &b) const {
return (a >= mod ? a - mod : a) == (b.a >= mod ? b.a - mod : b.a);
}
bool operator!=(const mint &b) const {
return (a >= mod ? a - mod : a) != (b.a >= mod ? b.a - mod : b.a);
}
mint operator-() const { return mint(0) - mint(*this); }
mint operator+() const { return mint(*this); }
mint pow(ULong n) const {
mint ret(1), mul(*this);
while (n > 0) {
if (n & 1) ret *= mul;
mul *= mul, n >>= 1;
}
return ret;
}
friend ostream &operator<<(ostream &os, const mint &b) {
return os << b.get();
}
friend istream &operator>>(istream &is, mint &b) {
Long t;
is >> t;
b = ArbitraryLazyMontgomeryModIntBase(t);
return (is);
}
mint inverse() const {
Int x = get(), y = get_mod(), u = 1, v = 0;
while (y > 0) {
Int t = x / y;
swap(x -= t * y, y);
swap(u -= t * v, v);
}
return mint{u};
}
UInt get() const {
UInt ret = reduce(a);
return ret >= mod ? ret - mod : ret;
}
static UInt get_mod() { return mod; }
};
// id に適当な乱数を割り当てて使う
template <int id>
using ArbitraryLazyMontgomeryModInt =
ArbitraryLazyMontgomeryModIntBase<int, unsigned int, long long,
unsigned long long, id>;
template <int id>
using ArbitraryLazyMontgomeryModInt64bit =
ArbitraryLazyMontgomeryModIntBase<long long, unsigned long long, __int128_t,
__uint128_t, id>;
#line 3 "modulo/mod-sqrt.hpp"
int64_t mod_sqrt(const int64_t &a, const int64_t &p) {
assert(0 <= a && a < p);
if (a < 2) return a;
using Mint = ArbitraryLazyMontgomeryModInt<409075245>;
Mint::set_mod(p);
if (Mint(a).pow((p - 1) >> 1) != 1) return -1;
Mint b = 1, one = 1;
while (b.pow((p - 1) >> 1) == 1) b += one;
int64_t m = p - 1, e = 0;
while (m % 2 == 0) m >>= 1, e += 1;
Mint x = Mint(a).pow((m - 1) >> 1);
Mint y = Mint(a) * x * x;
x *= a;
Mint z = Mint(b).pow(m);
while (y != 1) {
int64_t j = 0;
Mint t = y;
while (t != one) {
j += 1;
t *= t;
}
z = z.pow(int64_t(1) << (e - j - 1));
x *= z;
z *= z;
y *= z;
e = j;
}
return x.get();
}
/**
* @brief mod sqrt(Tonelli-Shanks algorithm)
* @docs docs/modulo/mod-sqrt.md
*/