Day 168-Q5 — 2026-10-03 | 離散対数(法が素数でなくてもよい BSGS。gcd を割って互いに素にし、途中の一致も拾う)

2026-10-03 赤色 Master (Phase 8+) ★★★★★★★★★ 数論(焦点帯 M3)。$a^x\equiv b\pmod m$ の最小の $x$ を $O(\sqrt m)$ で求める baby-step giant-step(BSGS)。教科書版は $\gcd(a,m)=1$ を仮定する。仮定が崩れると黙って誤答するので、$\gcd$ を割って互いに素に直す拡張版を、全数検査で固める

問題

$T$ 個のテストケースが与えられる。各ケースで整数 $a,b,m$ が与えられるので、

$$a^x\equiv b\pmod m$$

を満たす**最小の非負整数 $x$** を求めよ。存在しなければ $-1$ を出力せよ。ただし $a^0=1$($a=0$ のときも)とし、$m=1$ のときは常に $x=0$ とする。$a,b$ は $m$ で割った余りで考えてよい。

入力形式

T
a_1 b_1 m_1
...
a_T b_T m_T

制約

#条件
1$1\le T\le100$
2$1\le m\le10^9$
3$0\le a,b\le10^9$
4入力はすべて整数。$\gcd(a,m)=1$ とは限らない

入出力例

入力例1

4
2 8 12
2 3 7
4 0 8
6 1 9

出力例1

3
-1
2
0

($2^3=8$。$2^x\bmod7\in\{1,2,4\}$ なので 3 は作れない。$4^2=16\equiv0\pmod 8$。$6^0=1$)

入力例2

3
2 4 8
10 1 17
3 5 7

出力例2

2
0
5

($2^2=4$。$10^0=1$。$3^5=243\equiv5\pmod 7$)

背景と出典

BSGS は Shanks(1971)による離散対数の $O(\sqrt m)$ 解法と私は記憶している(未確認)。「$\gcd(a,m)\ne1$ でも使えるようにする拡張」は、競技プログラミング界で「拡張 BSGS」の名で広まった手法で、原典は特定できない(私の把握では中国のコミュニティ起源。未確認)。

この問題の核は「$a$ が $m$ と互いに素でないと、$a$ は $\bmod m$ で逆元を持たず、$a^{-s}$ を掛ける giant-step が使えない」ことへの対処。**$\gcd(a,m)=g$ を方程式から割り出し**、$m$ を $m/g$ に縮めて、互いに素になるまで繰り返す。割るたびに左辺の係数が増え、$x$ が 1 ずつ減る。

概念図

入力例 1 の (a,b,m) = (4,0,8) で gcd を割る手順開始a^x ≡ 0 (mod 8)k=1, cnt=0k≠bgcd(4,8)=4両辺と m を 4 で割るm=2, b=0, k=1·(4/4)=1, cnt=1k=1≠0gcd(4,2)=2もう一度 2 で割るm=1, b=0, k=1·(4/2)=2→0, cnt=2k=0=bk = b になった時点の cnt = 2 が答え(4^2 = 16 ≡ 0 mod 8)x = s·i − j(giant step i≥1、baby step j<s)。x = 0 は giant step に現れないので k = b で別に拾う教科書版 BSGS: gcd≠1 で 96416/583220 誤答、gcd=1 でも x=0 を見逃して 4385/351177 誤答

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

ヒント1(方向性)

$\gcd(a,m)=1$ なら、$x=si-j$($0\le j<s$、$s\approx\sqrt m$)とおいて $a^{si}\equiv b\,a^{j}$ と変形できる。右辺 $b\,a^j$ をすべて表に覚え(baby-step)、左辺 $a^{si}$ を $i=1,2,\dots$ で順に作って表と突き合わせる(giant-step)。

ヒント2(アプローチ)

$\gcd(a,m)=g>1$ のとき、$a^x\equiv b\pmod m$ が解を持つには $g\mid b$ が要る($g\mid m$ なので $a^x\equiv b$ の両辺を $g$ で割れる)。割ると $(a/g)\,a^{x-1}\equiv b/g\pmod{m/g}$。これを、$\gcd(a,m')=1$ になるまで繰り返す。左辺に残る係数 $k=\prod(a/g_t)$ と、割った回数 $\mathrm{cnt}$ を覚える。

ヒント3(誘導)

互いに素になったら $k\cdot a^{x'}\equiv b'\pmod{m'}$ を BSGS で解く($x=x'+\mathrm{cnt}$)。ただし割る途中で $k\equiv b'$ になるなら、その時点の $\mathrm{cnt}$ が答えの候補(小さい $x$ はこのループで拾う)。baby-step は $x'\ge0$ を、giant-step は $i\ge1$ から始めるので、$x'=0$($k\equiv b'$)は別に判定する。

模範解答 (Python)

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


def discrete_log(a, b, m):
    # 最小の x >= 0 で a^x ≡ b (mod m)。無ければ -1
    a %= m
    b %= m
    if m == 1:
        return 0
    k = 1 % m
    cnt = 0
    while True:
        if k == b:
            return cnt                      # a^cnt ≡ b を、gcd を割る途中でも検出する
        g = gcd(a, m)
        if g == 1:
            break
        if b % g:
            return -1
        b //= g
        m //= g
        k = k * (a // g) % m
        cnt += 1
    # ここから gcd(a, m) = 1。k * a^x ≡ b (mod m) を解く(baby-step giant-step)
    s = isqrt(m) + 1
    table = {}
    cur = b
    for j in range(s):                      # b * a^j を覚える(同じ値は大きい j を残す)
        table[cur] = j
        cur = cur * a % m
    step = pow(a, s, m)
    cur = k
    for i in range(1, s + 1):
        cur = cur * step % m                # k * a^(s i)
        if cur in table:
            x = i * s - table[cur]
            if x >= 0:
                return x + cnt
    return -1


def main():
    data = sys.stdin.buffer.read().split()
    t = int(data[0])
    out = []
    for i in range(t):
        a, b, m = int(data[1 + 3 * i]), int(data[2 + 3 * i]), int(data[3 + 3 * i])
        out.append(discrete_log(a, b, m))
    print("\n".join(map(str, out)))


if __name__ == "__main__":
    main()

計算量: 1 ケース $O(\sqrt m)$($m=10^9$ で辞書に約 3.2 万要素)。実測(Python 3.14.7、M1 Mac)で $T=100$、$m\approx10^8$〜$10^9$ が 0.5 s、$\gcd\ne1$ の大きい例 100 件が 0.1〜0.15 s。

Step-by-Step 解説

1互いに素なときの BSGS

$s=\lceil\sqrt m\rceil$ とし、$x=si-j$ と書く。$k\cdot a^{si-j}\equiv b$ を $a^j$ 倍して $k\,a^{si}\equiv b\,a^j$。右辺 $b\,a^j$($j=0..s-1$)を辞書に入れ(同じ値なら大きい $j$ に上書きして、$x=si-j$ を小さくする)、左辺を $i=1..s$ で順に作る。最初に一致した $i$ で $x=si-j$ が最小。$i=0$ から始めないのは $x=-j\le0$ を避けるため。

2$\gcd(a,m)>1$ を割って消す

$a^x\equiv b\pmod m$ で $g=\gcd(a,m)>1$、$x\ge1$ なら $g\mid a^x$、$g\mid m$ から $g\mid b$ が必要。満たさなければ、$x\ge1$ の解は無い。両辺と $m$ を $g$ で割ると $(a/g)\,a^{x-1}\equiv b/g\pmod{m/g}$。これを繰り返し、係数 $k=\prod(a/g_t)$ を積み、$\mathrm{cnt}$ を数える。$m$ が縮むたびに $\gcd(a,m)$ は真に減る($m$ の素因数のうち $a$ と共通のものが 1 つ以上消えるため、概算で高々 30 回)。

3割る途中の一致を見逃さない

割る前の $a^{\mathrm{cnt}}\equiv b$ が成立する小さい $x$($x<\mathrm{cnt}$)は、BSGS の探索対象 $x'\ge0$ の外になる。そこでループの先頭で $k\equiv b$(現在の $m$ で)を毎回調べ、成り立てば $\mathrm{cnt}$ を返す。この判定を落とすと、$m\le120$ の全数検査で 583220 通り中 4254 通りが誤る(実測)。

4互いに素でも落とし穴がある

教科書通りの BSGS($i=1..s$、$x=0$ の判定なし)は、$\gcd(a,m)=1$ の 351177 通りのうち 4385 通りで誤り、**そのすべてが答え $x=0$($b\equiv1$)を見逃していた**(実測)。$\gcd\ne1$ を含めると 583220 通り中 96416 通りで誤る。

5最小性

最小の $x$ を答えることは、全数検査($a,b<m+3$ の全組合せを反復で解いた最小値と比較)で確かめた。大きい $m$ では、返った $x$ が $a^x\equiv b$ を満たすこと(100 件)と、手前に解が無いこと(20 件の抜き取りで反復して確認)を実測した。

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

案計算量採否理由
$x=0,1,2,\dots$ と反復$O(\text{周期})\le O(m)$検算用のみ$m\le3000$ なら一瞬。$m=10^9$ は不可
教科書版 BSGS($\gcd=1$ 前提)$O(\sqrt m)$不採用$\gcd\ne1$ で 96416/583220 誤答(実測)。互いに素でも $x=0$ を見逃す
拡張 BSGS(本解)$O(\sqrt m+\log m)$採用全数 627860 通りが反復と一致
Pohlig–Hellman(群の位数が滑らかな場合)$O(\sum\sqrt{p_i^{e_i}})$不採用位数の素因数分解が要る。$m$ が一般だと準備が重い。$m$ が素数で $m-1$ が滑らかなら強い
Pollard の $\rho$(離散対数版)期待 $O(\sqrt m)$、メモリ $O(1)$不採用乱択で最小の $x$ が取れない。周期に注意が要る

前提が変わったら:

  • **$m$ が $10^{12}$ を超える**なら辞書が $10^6$ を超える。メモリを節約するなら $\rho$ 法(最小解でなくてよい場合)
  • **$m$ が素数で位数が滑らか**なら Pohlig–Hellman の方が速い(暗号の文脈では滑らかな位数は避ける。ここは競技の設定で別の話)
  • **複数の質問が同じ $a,m$ で $b$ だけ違う**なら、baby-step の表を使い回して giant-step を $b$ ごとに回す、という分け方がある(本問は $T\le100$ で個別に解く)

よくあるミス

ミス原因正しい書き方
$\gcd(a,m)=1$ を仮定する教科書の BSGS をそのまま写す$\gcd$ を割るループを前置(96416/583220 で誤答)
割る途中の $k\equiv b$ を見ないループ終了後の BSGS だけで十分と思うループ先頭で毎回判定(4254/583220 で誤答)
$x=0$($b\equiv1$)を見逃すgiant-step を $i=1$ から始めている$k\equiv b$ の判定が $x'=0$ を担当(互いに素の 4385/351177 が全部これ)
baby-step の辞書を最小の $j$ で残す「先に見たもの優先」と思う大きい $j$ で上書き($x=si-j$ が小さくなる向き)
$b\ge m$ のまま比較する入力を $m$ で割らない最初に b %= m

次のステップ

  • 発展1: 位数($a^x\equiv1$ の最小の $x\ge1$)を BSGS で求める($b=1$ で $x=0$ を除く)
  • 発展2: 同じ $a,m$ で $b$ が多数のとき、baby-step の表を共有する
  • 発展3: Pohlig–Hellman を実装し、$m-1$ が滑らかな素数 $m$ で BSGS と速度を比べる(未実施)

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

  • 全数検査(実測): $m=1..120$、$a,b=0..m+2$($m$ 以上の入力も含む)の全 627860 通りで、反復で最小の $x$ を求めた結果と不一致 0 件
  • 誤った方法(実測、$m\le120$、$a,b<m$ の全 583220 通り): 教科書版 BSGS が 96416 通りで誤答($\gcd(a,m)=1$ の 351177 通りに限ると 4385 通りで、その 4385 通りはすべて答えが $x=0$)。割る途中の $k\equiv b$ 判定を省いた版は 4254 通りで誤答
  • 無作為検査(実測): $m\le3000$、$a,b\le3m$ の 3000 件で反復と不一致 0 件
  • 大きい法(実測): $T=100$、$m\in[10^8,10^9]$、$b=a^x\bmod m$($x\le10^6$)で、返った $x$ が $a^x\equiv b$ を満たすこと 100/100。うち 20 件で、返った $x$ より手前に解が無いことを反復で確認(20/20)。$\gcd\ne1$ の例 100 件($m=g\cdot[10^7,10^8]$)も 100/100
  • 入力例(実測): 例 1 3 -1 2 0、例 2 2 0 5
  • 実行時間(実測): $T=100$、$m\approx10^9$ が 0.5 s、$\gcd\ne1$ の 100 件が 0.1〜0.15 s
  • 扱っていない範囲: $m>10^9$、$\gcd$ を割る回数の上界の証明、Pohlig–Hellman・$\rho$ 法との比較、原典(Shanks 1971 など)の確認、大きい $m$ での「手前に解が無い」の全件確認(20 件の抜き取りのみ)

自己評価

自分の回答

気づき・メモ