問題
正の整数 $m$ と、$Q$ 個の整数 $a_1,\dots,a_Q$($0\le a_i<m$)が与えられる。各 $i$ について、合同式
$$x^2\equiv a_i\pmod m,\qquad 0\le x<m$$
の解の個数と、解を 1 つ出力せよ(解が無ければ解として -1)。
入力形式
m Q
a_1 a_2 ... a_Q
出力形式: $Q$ 行。$i$ 行目に「解の個数」「解の 1 つ(無ければ -1)」を空白区切りで。解が複数あるときはどれを出してもよい
制約
| # | 制約 |
|---|---|
| 1 | $1\le m\le10^{12}$ |
| 2 | $1\le Q\le5\times10^4$ |
| 3 | $0\le a_i<m$ |
入出力例
入力例1
12 6
0 1 4 9 3 8出力例1
2 0
4 1
4 4
2 9
0 -1
0 -1($x^2\equiv0$ は $x=0,6$。$x^2\equiv1$ は $1,5,7,11$。$x^2\equiv3$ は解なし)
入力例2
16 4
1 9 0 4出力例2
4 1
4 5
4 0
4 2($2^4$ では単数 $a\equiv1\pmod8$ の解は 4 個。$a=0$ は $x=0,4,8,12$、$a=4$ は $x=2,6,10,14$)
入力例3
1000000000000 3
0 1 999999999999出力例3
1000000 0
8 1
0 -1($m=2^{12}5^{12}$。$x^2\equiv0$ は $x$ が $2^65^6=10^6$ の倍数で $10^6$ 個。$x^2\equiv1$ は $2^{12}$ で 4 個 × $5^{12}$ で 2 個 = 8 個。3 例とも総当たりと一致)
背景と出典
- Hensel の補題: $f(y)\equiv0\pmod{p^j}$ で $f'(y)\not\equiv0\pmod p$ なら、$y\leftarrow y-f(y)/f'(y)$ で $p^{2j}$ を法とする解に持ち上がる($p$ 進数のニュートン法)。$f(y)=y^2-a$ では $f'(y)=2y$ なので、$p$ が奇数で $p\nmid a$ のときに使える
- $p=2$ の場合: 奇数 $a$ について $y^2\equiv a\pmod{2^j}$ の解の個数は $j=1$ で 1、$j=2$ で $a\equiv1\pmod4$ なら 2、$j\ge3$ で $a\equiv1\pmod8$ なら 4(それ以外は 0)。整数論の標準的な結果で、本問では $2^{13}$ までの総当たりで確かめた
- Tonelli–Shanks と Cipolla: どちらも素数を法とする平方根の標準的な方法。Tonelli–Shanks は $p-1=q\cdot2^s$ の $s$ について最悪 $O(s^2)$ 回の 2 乗が要り、Cipolla は $\mathbb F_p[\sqrt w]$ での $O(\log p)$ 回のべき乗で $s$ に依らない、と私は理解している。本問ではこの差を実測した($s=30$ の素数で Tonelli 5.12 s、Cipolla 3.41 s)
- 本問は自作。素数を法とする平方根(Tonelli、Day 一覧に既出)と CRT を、素数べき・$p\mid a$・$p=2$ の場合分けまで含めた形にした
概念図
ヒント(クリックで段階的に開示)
ヒント1(方向性)
CRT により、$m=\prod p^k$ の各 $p^k$ で独立に解ける。解の個数は各素数べきでの個数の積、解は CRT で 1 つに合わせる。
ヒント2(アプローチ)
$p^k$ を法として、$a$ を $p^v\cdot a'$($p\nmid a'$)と書く。
- $a\equiv0\pmod{p^k}$: $x$ が $p^{\lceil k/2\rceil}$ の倍数なら $x^2\equiv0$。個数は $p^{\lfloor k/2\rfloor}$
- $v$ が奇数: 解なし
- $v$ が偶数: $x=p^{v/2}y$ と置くと $y^2\equiv a'\pmod{p^{k-v}}$。$y$ は $p^{k-v/2}$ を法として決まるので、個数は「$p^{k-v}$ での単数の平方根の個数」$\times p^{v/2}$
ヒント3(誘導)
単数 $a'$ の平方根(法 $p^j$):
- 奇素数: オイラーの基準で平方剰余か調べ、Tonelli–Shanks(または Cipolla)で法 $p$ の解を出し、$y\leftarrow y-(y^2-a')(2y)^{-1}$ で $p^2,p^4,\dots$ と持ち上げる。個数は 2
- $p=2$: $j\ge3$ なら $a'\equiv1\pmod8$ のとき $y=1$ から始め、$y^2\not\equiv a'\pmod{2^{i+1}}$ なら $y\leftarrow y+2^{i-1}$ で 1 ビットずつ持ち上げる。個数は 4
模範解答 (Python)
模範解答を表示する(まず自力で解いてから)
import sys
def factorize(m):
fs = []
d = 2
while d * d <= m:
if m % d == 0:
k = 0
while m % d == 0:
m //= d
k += 1
fs.append((d, k))
d += 1 if d == 2 else 2
if m > 1:
fs.append((m, 1))
return fs
_TS = {}
def sqrt_mod_p(a, p):
"""奇素数 p、a は p と互いに素な平方剰余。p-1 = q·2^s の s が小さければ Tonelli–Shanks、
大きければ Cipolla(Tonelli は最悪 s^2/2 回の 2 乗が要り、s=30 で遅い)"""
if p % 4 == 3:
return pow(a, (p + 1) // 4, p)
if p not in _TS:
q, s = p - 1, 0
while q % 2 == 0:
q //= 2
s += 1
z = 2
while pow(z, (p - 1) // 2, p) != p - 1:
z += 1
_TS[p] = (q, s, pow(z, q, p))
q, s, c = _TS[p]
if s > 24:
return cipolla(a, p)
m, t, r = s, pow(a, q, p), pow(a, (q + 1) // 2, p)
while t != 1:
i, t2 = 0, t
while t2 != 1:
t2 = t2 * t2 % p
i += 1
b = pow(c, 1 << (m - i - 1), p)
m, c, t, r = i, b * b % p, t * b * b % p, r * b % p
return r
def cipolla(a, p):
"""w = t^2 - a が非剰余になる t を探し、F_p[√w] で (t + √w)^((p+1)/2) を計算する"""
t = 1
while True:
w = (t * t - a) % p
if pow(w, (p - 1) // 2, p) == p - 1:
break
t += 1
e = (p + 1) // 2
x0, x1 = 1, 0
b0, b1 = t, 1
while e:
if e & 1:
x0, x1 = (x0 * b0 + x1 * b1 % p * w) % p, (x0 * b1 + x1 * b0) % p
b0, b1 = (b0 * b0 + b1 * b1 % p * w) % p, 2 * b0 * b1 % p
e >>= 1
return x0
def unit_root(a, p, j):
"""p と互いに素な a について y^2 ≡ a (mod p^j) の (解の個数, 解の 1 つ)。j >= 1"""
if p == 2:
if j == 1:
return 1, 1
if j == 2:
return (2, 1) if a % 4 == 1 else (0, None)
if a % 8 != 1:
return 0, None
y = 1 # mod 8 の解。1 ビットずつ持ち上げる
for i in range(3, j): # y は mod 2^i の解 → mod 2^(i+1) の解に
if (y * y - a) % (1 << (i + 1)):
y += 1 << (i - 1)
return 4, y % (1 << j)
if pow(a % p, (p - 1) // 2, p) != 1:
return 0, None
y = sqrt_mod_p(a % p, p)
pj = p ** j
mod = p
while mod < pj: # Hensel: 精度を 2 倍ずつ(ニュートン法)
mod = min(mod * mod, pj)
y = (y - (y * y - a) * pow(2 * y, -1, mod)) % mod
return 2, y
def solve_prime_power(a, p, k):
pk = p ** k
a %= pk
if a == 0:
return p ** (k // 2), 0
v = 0
while a % p == 0:
a //= p
v += 1
if v % 2:
return 0, None
cnt, y = unit_root(a, p, k - v)
if cnt == 0:
return 0, None
h = v // 2
return cnt * p ** h, (p ** h * y) % pk
def main():
data = sys.stdin.buffer.read().split()
m, q = int(data[0]), int(data[1])
fs = factorize(m)
mods = [p ** k for p, k in fs]
# CRT の係数(M/pk の pk を法とする逆元)を前計算
coef = [(m // pk) * pow(m // pk, -1, pk) % m for pk in mods]
out = []
for i in range(q):
a = int(data[2 + i])
total, x = 1, 0
for (p, k), c in zip(fs, coef):
cnt, r = solve_prime_power(a, p, k)
if cnt == 0:
total = 0
break
total *= cnt
x += r * c
if total == 0:
out.append("0 -1")
else:
out.append(f"{total} {x % m}")
print("\n".join(out))
main()
計算量: 素因数分解 $O(\sqrt m)$ を 1 回。各クエリは素因数ごとに $O(\log p+s^2)$(Tonelli)または $O(\log p)$(Cipolla)と Hensel の $O(\log k)$ 回の逆元。$Q=10^5$(制約の 2 倍)で、$m$ が素数のとき 2.29〜3.53 s、合成数のとき 0.44〜0.73 s(Python 3.14.7、M1 MacBook Pro 8GB、負荷平均 11〜18 での実測、起動込み)。制約は余裕を見て $Q\le5\times10^4$ とした。
Step-by-Step 解説
1CRT で素数べきに分ける
$m=\prod p_j^{k_j}$ なら、$x^2\equiv a\pmod m$ は各 $p_j^{k_j}$ での合同式の組と 1 対 1。個数は積、解は $x=\sum r_j\cdot c_j$($c_j\equiv1\pmod{p_j^{k_j}}$、他の法で 0)。$c_j$ は $a$ に依らないので前計算する。
2$a\equiv0$ のとき
$x^2\equiv0\pmod{p^k}$ ⇔ $p^{\lceil k/2\rceil}\mid x$。$0\le x<p^k$ でその倍数は $p^{k-\lceil k/2\rceil}=p^{\lfloor k/2\rfloor}$ 個。入力例 3 の $10^6$ がこれ。1 個と数えるのがよくあるミス($m\le600$ の全 180300 組のうち 3925 組で誤る、実測)。
3$p\mid a$ だが $a\not\equiv0$
$a=p^va'$。$x^2$ の $p$ 進付値は偶数なので、$v$ が奇数なら解なし。$v$ が偶数なら $x=p^{v/2}y$ で $p\nmid y$ が必要、$y^2\equiv a'\pmod{p^{k-v}}$。$x$ は $p^k$ を法とするので $y$ は $p^{k-v/2}$ を法として数え、$p^{k-v}$ での 1 つの解に対し $p^{v/2}$ 通りの持ち上げ方がある。
4単数の平方根と Hensel
奇素数なら解は $\pm y$ の 2 個($p^j$ を法としても 2 個のまま)。$f(y)=y^2-a$、$f'(y)=2y$ は $p$ と互いに素なので、ニュートン法 $y\leftarrow y-f(y)/f'(y)$ が $p^j\to p^{2j}$ と精度を倍にする。$p=2$ では $f'(y)=2y$ が 2 で割れて Hensel がそのまま使えないので、mod 8 から 1 ビットずつ持ち上げ、個数は 4($\pm y,\pm y+2^{j-1}$)。
5素数を法とする平方根の使い分け
$p\equiv3\pmod4$ なら $a^{(p+1)/4}$ で一発。それ以外は Tonelli–Shanks だが、$p-1$ の 2 べき $s$ が大きいと内側のループが $O(s^2)$。$s>24$ では Cipolla に切り替える(閾値は $s=20$ で Tonelli 3.12 s・Cipolla 3.92 s、$s=30$ で 5.12 s・3.41 s という実測から決めた)。
迷った他案と採らなかった理由
| 案 | 1 クエリの計算量 | 採否 | 理由 |
|---|---|---|---|
| 総当たり $x=0..m-1$ | $O(m)$ | 検算用のみ | $m\le600$ と $2^{13},3^8,5^5,7^4,11^3$ |
| 法 $p$ だけ見て $1+(a/p)$ 個とする | $O(\log p)$ | 不採用(誤り) | 180300 組中 25140 組で誤答(実測) |
| $p=2$ を奇素数と同じく 2 個とする | — | 不採用(誤り) | 8396 組で誤答(実測)。入力例 2 で 4 個のところを 2 個 |
| $a\equiv0$ を 1 個とする | — | 不採用(誤り) | 3925 組で誤答(実測) |
| Tonelli–Shanks だけ | $O(\log p+s^2)$ | 不採用($s$ が大きいと遅い) | $p=901943132161$($s=30$)で 5.12 s($Q=10^5$、実測) |
| Cipolla だけ | $O(\log p)$($\mathbb F_{p^2}$ の乗算) | 不採用($s$ が小さいと遅い) | $p=999999999989$($s=2$)で 5.61 s、Tonelli は 2.24 s(実測) |
| $s$ で使い分け(本解) | 小さい方 | 採用 | 最悪 3.53 s($Q=10^5$) |
前提が変わったら
- $m\le10^{18}$ なら試し割りでは分解できない。Pollard の ρ(Day 一覧に既出)で分解し、残りは同じ
- 解をすべて列挙するなら、各素数べきの解の集合($a\equiv0$ なら $p^{\lfloor k/2\rfloor}$ 個)の直積を CRT で組む。個数は最大で $m$ 程度になりうるので、出力量が制約になる
- $x^k\equiv a$(一般の $k$ 乗根)なら、法 $p$ での $k$ 乗根(Adleman–Manders–Miller)と、$p\mid k$ のときの持ち上げの場合分けが増える
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| $a\equiv0\pmod{p^k}$ の解を 1 個とする | 「$x=0$ だけ」と思い込む | $p^{\lfloor k/2\rfloor}$ 個。$m=16$ なら 0,4,8,12 の 4 個 |
| $p=2$ にも Hensel をそのまま使う | $f'(y)=2y$ が 2 の倍数 | mod 8 から 1 ビットずつ。個数は 4 |
| $p\mid a$ で $v$ の偶奇を見ない | 単数の場合の式を流用 | $v$ 奇数なら解なし。偶数なら $x=p^{v/2}y$ |
| $y$ の持ち上げの個数 $p^{v/2}$ を掛け忘れる | $y$ と $x$ の法の違いを見落とす | $y$ は $p^{k-v/2}$ を法として数える |
| Tonelli の非剰余 $z$ をクエリごとに探す | 関数をそのまま呼ぶ | 素数ごとに 1 回だけ。$s=30$ で 10.77 s → 5.30 s(p ** j の再計算をやめた分も含む、実測) |
次のステップ
- 発展1: 解をすべて列挙する版を作り、個数の式と照合する
- 発展2: $x^3\equiv a\pmod{p^k}$ で、$p=3$ のときの持ち上げの場合分けを総当たりで調べる
- 発展3: Pollard の ρ と組み合わせて $m\le10^{18}$ に広げる
検証ログ(本セッションで実行した数値)
- 総当たりとの照合(実測): $m=1\sim600$ のすべての $a$(計 180300 組)で、解の個数が一致し、出力した解が $x^2\equiv a$ を満たす。素数べき $2^{13},3^8,5^5,7^4,11^3$ の全 $a$ でも一致
- Cipolla の経路(実測): 使い分けの閾値を外して常に Cipolla にした版でも $m\le300$ の全 45150 組が一致(閾値 $s>24$ は小さい素数では通らないため)
- 大きな $m$(実測、$Q=10^5$、半分は $r^2\bmod m$): $m=901943132161,999999999989,2^{39},200560490130(=2\cdot3\cdots31),999999999999,10^{12}$ で、出力した解がすべて合同式を満たし、平方数のクエリで「解なし」を返したものは 0
- 誤った方法(実測、$m\le600$ の 180300 組): 法 $p$ だけ見る 25140、$p=2$ を 2 個とする 8396、$a\equiv0$ を 1 個とする 3925 組で誤答
- 実行時間(実測、$Q=10^5$、起動込み): 素数 $m$ で Tonelli のみ 2.24〜5.12 s、Cipolla のみ 3.41〜5.61 s、使い分け 2.29〜3.53 s。合成数 $m$ で 0.44〜0.73 s
- 扱っていない範囲: $m>10^{12}$(Pollard の ρ)、全解の列挙、一般の $k$ 乗根