Day 179-Q5 — 2026-10-15 | 法 $m\le10^{18}$ の平方根を全部求める(Pollard ρ で素因数分解し、奇素数べきは Tonelli–Shanks+Hensel、2 べきは 1 ビットずつ、最後に CRT で全組み合わせ)

2026-10-15 赤色 Master (Phase 8+) ★★★★★★★★★ 数論(焦点帯 M3)。Day176-Q5 では $m\le600$ の小さい法で平方根の個数を数えた($a\equiv0$ や $p$ の付値の場合分けが中心)。今回は $\gcd(a,m)=1$ に絞る代わりに、法を $10^{18}$ まで広げて解を全部作る。素因数分解が律速になり、解の個数は最大 $2^{15}$ 程度まで増える

問題

$Q$ 個の問い合わせがある。各問い合わせでは整数 $a,m$ が与えられる。$x^2\equiv a\pmod m$、$0\le x<m$ を満たす $x$ の個数と、そのうち最小のものを出力せよ(解が無ければ 0 -1)。

入力形式

Q
a_1 m_1
...
a_Q m_Q

制約

#制約
1$1\le Q\le200$
2$1\le m_i\le10^{18}$、$0\le a_i<m_i$、$\gcd(a_i,m_i)=1$($m_i=1$ のときは $a_i=0$)
3出力は 1 問い合わせ 1 行に 個数 最小の解

入出力例

入力例1

4
1 8
2 7
4 15
3 5

出力例1

4 1
2 3
4 2
0 -1

($x^2\equiv1\pmod8$ は $1,3,5,7$ の 4 個。奇素数なら解は 2 個だが、$2^3$ 以上では 4 個になる。$x^2\equiv4\pmod{15}$ は $2,7,8,13$)

入力例2

3
2809 614889782588491410
1 1000000000000000000
2 999999999999999989

出力例2

16384 53
8 1
0 -1

(1 行目の法は 47 までの素数 15 個の積で、$2$ の因子は解 1 個・奇素数 14 個はそれぞれ 2 個、合わせて $2^{14}=16384$ 個。2 行目は $2^{18}\cdot5^{18}$ で $4\times2=8$ 個。3 行目は素数 $p\equiv5\pmod8$ で 2 は平方非剰余。3 例とも全探索・独立な検算と一致)

背景と出典

  • 法 $p$ の平方根の Tonelli–Shanks 法、Hensel の補題による持ち上げ、2 べきでの解の個数($2^e$, $e\ge3$ で 4 個)は数論の標準的な事実(教科書名は挙げない。本セッションでは原典を確認していない)
  • Pollard の ρ 法(1975)と Brent の改良(1980)は記憶による年号で、未確認。Miller–Rabin を基数 2〜37 の 12 個で回すと $3.3\times10^{24}$ 未満で決定的、という事実も記憶による(未確認)
  • 本問の問題文・制約・入力例は自作

概念図

x² ≡ 1 (mod 120)、120 = 2³ · 3 · 5:素数べきごとの解の直積が CRT で法 120 の解になる mod 8(2³:4 個) x ≡ 1 x ≡ 3 x ≡ 5 x ≡ 7 × mod 3(2 個) x ≡ 1 x ≡ 2 × mod 5(2 個) x ≡ 1 x ≡ 4 = mod 120(4 × 2 × 2 = 16 個) 1 11 19 29 31 41 49 59 61 71 79 89 91 101 109 119 2³ 以上で 4 個になる理由:奇数の平方は法 8 で必ず 1、r と r + 2^(e−1) は平方が同じ((r+2^(e−1))² = r² + r·2^e + 2^(2e−2)) 奇素数べき:法 p の ±r(Tonelli–Shanks)を Hensel(ニュートン法)で p^e へ持ち上げる。2r が p で割れないので 1 対 1 m ≤ 10^18 の素因数分解は Pollard ρ(Brent)+ Miller–Rabin。解の個数は最大 4·2^13 = 32768

ヒント(クリックで段階的に開示)

ヒント1(方向性)

CRT(中国剰余定理)で、$m=\prod p_i^{e_i}$ なら「法 $m$ の解」と「各 $p_i^{e_i}$ の解の組」が 1 対 1 に対応する。個数は積、解は組み合わせで作れる。まず $m$ を素因数分解する($10^{18}$ なので試し割りでは無理)。

ヒント2(アプローチ)
  • 奇素数 $p$: $a^{(p-1)/2}\equiv1$ なら法 $p$ の解 $\pm r$ があり(Tonelli–Shanks)、$\gcd(a,p)=1$ なので $p^e$ へ 1 通りずつ持ち上がる($2r\not\equiv0$ だから Hensel が使える)。解は 2 個
  • $p=2$: $e=1$ は 1 個、$e=2$ は $a\equiv1\pmod4$ のとき 2 個、$e\ge3$ は $a\equiv1\pmod8$ のとき 4 個($\pm r,\ \pm r+2^{e-1}$)
ヒント3(誘導)
  • Hensel: $r\leftarrow r-(r^2-a)\cdot(2r)^{-1}\bmod p^{k}$ で、正しい桁数が倍々に伸びる(ニュートン法)
  • 2 べき: $r=1$ から始め、$k=3,4,\dots,e-1$ で $r^2\not\equiv a\pmod{2^{k+1}}$ なら $r$ に $2^{k-1}$ を足す
  • 素因数分解は Miller–Rabin で素数判定し、合成数なら Brent 版 Pollard ρ(gcd を 128 歩ごとにまとめる)で割る

模範解答 (Python)

模範解答を表示する(まず自力で解いてから)
import sys
from math import gcd
from random import randrange


def is_prime(n):
    if n < 2:
        return False
    for p in (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37):
        if n % p == 0:
            return n == p
    d, s = n - 1, 0
    while d % 2 == 0:
        d //= 2; s += 1
    for a in (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37):   # n < 3.3e24 で決定的
        x = pow(a, d, n)
        if x == 1 or x == n - 1:
            continue
        for _ in range(s - 1):
            x = x * x % n
            if x == n - 1:
                break
        else:
            return False
    return True


def rho(n):
    """Brent 版 Pollard ρ。gcd を 128 歩ごとにまとめて取る"""
    if n % 2 == 0:
        return 2
    while True:
        c = randrange(1, n)
        y, m, g, r, q = randrange(n), 128, 1, 1, 1
        while g == 1:
            x = y
            for _ in range(r):
                y = (y * y + c) % n
            k = 0
            while k < r and g == 1:
                ys = y
                for _ in range(min(m, r - k)):
                    y = (y * y + c) % n
                    q = q * abs(x - y) % n
                g = gcd(q, n)
                k += m
            r *= 2
        if g == n:                      # まとめすぎて n になったら 1 歩ずつ戻す
            g = 1
            while g == 1:
                ys = (ys * ys + c) % n
                g = gcd(abs(x - ys), n)
        if g != n:
            return g


def factor(n, out):
    for p in (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47):
        while n % p == 0:
            out[p] = out.get(p, 0) + 1
            n //= p
    stack = [n] if n > 1 else []
    while stack:
        x = stack.pop()
        if x == 1:
            continue
        if is_prime(x):
            out[x] = out.get(x, 0) + 1
            continue
        d = rho(x)
        stack.append(d); stack.append(x // d)
    return out


def sqrt_mod_prime(a, p):
    """Tonelli–Shanks。a は p の平方剰余(p 奇素数)"""
    if p % 4 == 3:
        return pow(a, (p + 1) // 4, p)
    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
    m, c, t, r = s, pow(z, q, p), pow(a, q, p), pow(a, (q + 1) // 2, p)
    while t != 1:
        i, tt = 0, t
        while tt != 1:
            tt = tt * tt % 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 roots_prime_power(a, p, e):
    """x^2 ≡ a (mod p^e) の解(gcd(a,p)=1)"""
    pe = p ** e
    a %= pe
    if p == 2:
        if e == 1:
            return [1]
        if e == 2:
            return [1, 3] if a % 4 == 1 else []
        if a % 8 != 1:
            return []
        r = 1                              # 法 8 で 1。k ビット目ずつ直す
        for k in range(3, e):
            if (r * r - a) % (1 << (k + 1)):
                r += 1 << (k - 1)
        h = 1 << (e - 1)
        return sorted({r % pe, (-r) % pe, (r + h) % pe, (-r + h) % pe})
    if pow(a % p, (p - 1) // 2, p) != 1:
        return []
    r = sqrt_mod_prime(a % p, p)
    k = 1
    while k < e:                           # Hensel(ニュートン)で桁を倍々に
        k = min(2 * k, e)
        mod = p ** k
        r = (r - (r * r - a) * pow(2 * r, -1, mod)) % mod
    return [r, pe - r] if r != pe - r else [r]


def main():
    data = sys.stdin.buffer.read().split()
    q = int(data[0])
    out = []
    for i in range(q):
        a, m = int(data[1 + 2 * i]), int(data[2 + 2 * i])
        if m == 1:
            out.append("1 0")
            continue
        fs = factor(m, {})
        sols, mod = [0], 1
        for p, e in sorted(fs.items()):
            rs = roots_prime_power(a, p, e)
            if not rs:
                sols = []
                break
            pe = p ** e
            inv = pow(mod, -1, pe)          # CRT: x = s + mod * ((r - s) * inv mod pe)
            sols = [s + mod * ((r - s) * inv % pe) for s in sols for r in rs]
            mod *= pe
        if sols:
            out.append(f"{len(sols)} {min(sols)}")
        else:
            out.append("0 -1")
    print("\n".join(out))


main()

計算量: 素因数分解は 1 個あたり期待 $O(m^{1/4})$ 回の乗算(Pollard ρ)、平方根は素数べきごとに $O(\log^2 p)$、組み合わせは解の個数(最大 $2^{15}$ 程度)に比例。$Q=100$ で、$5\times10^8$〜$10^9$ の素数 2 つの積(ρ の最悪に近い)0.44 s、$10^{17}$〜$10^{18}$ の乱数 0.03 s、解が 16384 個ずつある素数 15 個の積 0.26 s(Python 3.14.8、Apple M5 Pro、起動込み)。

Step-by-Step 解説

1CRT で分ける

$m=\prod p_i^{e_i}$ なら、$x\bmod m$ と $(x\bmod p_i^{e_i})_i$ は 1 対 1。$x^2\equiv a$ は各法で独立に成り立てばよいので、解の集合は各素数べきの解の集合の直積。個数は積で、どこか 1 つでも 0 なら全体も 0。

2奇素数べき

$\gcd(a,p)=1$ で $a$ が法 $p$ の平方剰余なら、法 $p$ の解は $\pm r$ の 2 個。$f(x)=x^2-a$ の微分 $2r$ は $p$ で割り切れないので、Hensel の補題で各解が法 $p^e$ の解にちょうど 1 つずつ持ち上がる。ニュートン法 $r\leftarrow r-f(r)/f'(r)$ は正しい桁数を倍にするので、$\lceil\log_2e\rceil$ 回で済む。

32 べき

奇数 $x$ の平方は法 8 で必ず 1。だから $e\ge3$ では $a\equiv1\pmod8$ が必要十分で、解は $\pm r,\ \pm r+2^{e-1}$ の 4 個($(r+2^{e-1})^2=r^2+r2^e+2^{2e-2}\equiv r^2$)。$r$ は法 $2^{k+1}$ で合わなければ $2^{k-1}$ を足して直す($(r+2^{k-1})^2\equiv r^2+r2^k\pmod{2^{k+1}}$ で、$r$ が奇数なので $2^k$ の桁が反転する)。

4素因数分解

$10^{18}$ の試し割りは $10^9$ 回で無理。小さい素数で割ったあと、Miller–Rabin で素数なら終わり、合成数なら Pollard ρ で約数を 1 つ見つけて両方を再帰。ρ は $x\mapsto x^2+c$ の列の衝突を $\gcd(|x-y|,n)$ で見つける方法で、最小の素因数 $p$ について期待 $O(\sqrt p)$ 歩。gcd を 128 歩ごとにまとめ、まとめすぎて $n$ になったら 1 歩ずつ戻る(Brent の工夫)。

5組み合わせて最小を取る

CRT を 1 素数べきずつ足していく: これまでの解 $s$(法 $M$)と新しい解 $r$(法 $p^e$)から $x=s+M\cdot((r-s)M^{-1}\bmod p^e)$。全組み合わせのリストを作り最小を取る。解の個数は、$m\le10^{18}$ だと $2^3$ と奇素数 13 個($3..43$)の $4\cdot2^{13}=32768$ が最大(奇素数 14 個に $2^3$ を掛けると $2.5\times10^{18}$ で超える)。

迷った他案と採らなかった理由

案計算量採否理由
$0..m-1$ を全部試す$O(m)$検算用のみ$m\le1500$ の全 684182 問い合わせで使った
法 $p$ の根 $\pm r$ をそのまま法 $p^e$ の解にする(Hensel を忘れる)—不採用(誤り)684182 問い合わせ中 21080 で誤る(実測)
2 べきも奇素数と同じく解 2 個—不採用(誤り)5913 で誤る(実測)。$2^e$($e\ge3$)は 4 個
Cipolla 法で法 $p$ の平方根$O(\log p)$不採用正しく速いが、$p\equiv3\pmod4$ の近道と Tonelli–Shanks で十分速かった(素因数分解が律速)
試し割り $\sqrt m$$O(10^9)$不採用$10^{18}$ では間に合わない
Pollard ρ(Brent)+Miller–Rabin(本解)期待 $O(m^{1/4})$採用最悪に近い半素数 100 個で 0.44 s

前提が変わったら

  • $\gcd(a,m)\ne1$ を許すなら、Day176-Q5 の場合分け($a\equiv0$ なら $p^{\lfloor e/2\rfloor}$ 個、付値が奇数なら 0 個、偶数なら単数部分の解×$p^{v/2}$ 個)が要る。解の個数は $p^{\lfloor e/2\rfloor}$ で巨大になりうるので「全部作る」は無理になり、最小の解だけを求める工夫が要る
  • $Q$ が $10^5$なら、半素数での ρ(1 個 4 ms 程度)が律速で 400 s 級になる。Python では $m\le10^{18}$ の大量の素因数分解は厳しく、C++ か、$m$ の素因数分解が入力で与えられる形にする
  • 解の和や個数だけなら全組み合わせを作らずに積で出せる。最小の解は組み合わせが要る(CRT の値は成分ごとに単調でない)

よくあるミス

ミス原因正しい書き方
$2^e$ の解を 2 個とする奇素数と同じと思う$e\ge3$ は 4 個、$e=2$ は 2 個、$e=1$ は 1 個
Hensel を 1 桁ずつ $e$ 回間違いではないが遅いニュートン法で倍々
ρ の gcd を毎歩取る素直に書くまとめて取り、$n$ になったら戻る
Miller–Rabin の基数を少なくする「ほぼ確実」で済ませる$10^{18}$ 級は 2〜37 の 12 基数で決定的(記憶による事実)
CRT の途中で $M$ の逆元を毎回 pow で取り直さない/取り違える$M$ と $p^e$ を逆にする$M^{-1}\bmod p^e$

次のステップ

  • 発展1: $\gcd(a,m)\ne1$ を許して最小の解だけを求める(解の個数が巨大でも、各素数べきの解を等差数列の和集合として持ち、CRT の最小値を探す)
  • 発展2: $x^k\equiv a$($k$ 乗根)に広げる。法 $p$ では離散対数(Day174-Q5)や Adleman–Manders–Miller 法と記憶している方法が要る
  • 発展3: ρ を Montgomery 乗算風に書き換え、半素数 100 個の 0.44 s がどこまで縮むか測る

検証ログ(本セッションで実行した数値)

  • 全探索との一致(実測): $1\le m\le1500$、$0\le a<m$、$\gcd(a,m)=1$ の全 684182 問い合わせで、個数と最小の解が $0..m-1$ の全探索と全件一致(解の個数の分布: 0 個 478107、1 個 2、2 個 109886、4 個 79388、8 個 15273、16 個 1510、32 個 16)
  • 独立な検算(実測): 各素数べきが $2\times10^4$ 以下の素数べきの積で $10^{12}<m\le10^{18}$ の 400 問い合わせ。素数べきごとの解を総当たりで作って CRT したものと、解の集合が全件一致(解の個数は最大 512)
  • 誤った方法(実測、同じ 684182 問い合わせ): Hensel を忘れる 21080、2 べきを 2 個とする 5913 で誤る
  • 時間(実測、起動込み、$Q=100$): 半素数($5\times10^8$〜$10^9$ の素数 2 つ)0.44 s、$10^{17}$〜$10^{18}$ の乱数 0.03 s、素数 15 個の積(解 16384 個ずつ)0.26 s、$3^{37}$・$2^{59}$・$(10^9+7)^2$ と大きな素数の混在 0.02 s。全出力で(最小の解)$^2\equiv a$ を確認
  • 扱っていない範囲: 原典の確認、$\gcd(a,m)\ne1$、$k$ 乗根

自己評価

自分の回答

気づき・メモ