Day 169-Q5 — 2026-10-04 | Pohlig–Hellman(p−1 が小さい素因数だけなら、10^18 の素数でも離散対数が解ける。g が原始根でないときの最小解)

2026-10-04 赤色 Master (Phase 8+) ★★★★★★★★★ 数論・代数(焦点帯 M3)。$g^x\equiv h\pmod p$ を、$g$ の位数 $n$ の素因数分解 $n=\prod q^e$ に沿って「位数 $q$ の小さな離散対数」に分け、$q$ 進の桁ごとに解いて CRT で戻す。位数を $p-1$ と決めつけると、$g$ が原始根でないとき最小解を外す

問題

$T$ 個の質問に答えよ。各質問では素数 $p$ と整数 $g,h$ が与えられる。

$$g^x\equiv h\pmod p$$

を満たす最小の非負整数 $x$ を出力せよ。存在しなければ -1 を出力せよ。

ただし、$p-1$ の素因数はすべて $10^5$ 以下であることが保証される。

入力形式

T
p_1 g_1 h_1
...
p_T g_T h_T

制約

#条件
1$1\le T\le100$
2$2\le p\le10^{18}$、$p$ は素数
3$p-1$ の素因数はすべて $10^5$ 以下
4$1\le g,h<p$
5入力はすべて整数

入出力例

入力例1

5
13 2 3
13 3 2
7 2 2
998244353 3 5
998244353 9 3

出力例1

4
-1
1
109353319
-1

($2^4=16\equiv3$。$3$ の冪は $3,9,1$ の繰り返しで $2$ は出ない。$7$ を法とする $2$ の位数は 3 なので、$x=1$ が最小。$998244353-1=2^{23}\cdot7\cdot17$ で、$9=3^2$ の冪は平方剰余だけを動き、$3$ は平方非剰余なので届かない)

背景と出典

Pohlig–Hellman は Pohlig と Hellman(1978)の離散対数アルゴリズム(私の記憶。原典は本セッションでは未確認)。群の位数が小さな素因数だけでできていると、離散対数は最大の素因数 $q$ について $O(\sqrt q)$ 程度で解ける。暗号で使う素数 $p$ に「$p-1$ が大きな素因数を持つ」こと(安全素数など)が求められるのは、この攻撃を避けるため、と理解している。

この問題の中核は 3 つ。

  1. $g$ の位数 $n$ を先に求める。$p-1$ の素因数 $q$ で、$g^{n/q}=1$ である限り $n$ を $q$ で割る
  2. 存在判定: $\mathbb{Z}_p^*$ は巡回群なので、$h\in\langle g\rangle\iff h^n=1$
  3. 最小解: 解は $\bmod n$ でただ 1 つに決まるので、$[0,n)$ の代表元が最小。$n$ を $p-1$ と取り違えると、$\bmod(p-1)$ の答えを出してしまう

前日の Day168-Q5 は合成数の法での拡張 BSGS($O(\sqrt m)$)だった。今回は法が素数で $10^{18}$ まで大きく、BSGS の $\sqrt p=10^9$ は不可能。代わりに $p-1$ の滑らかさを使う。

概念図

入力例 p = 998244353, g = 3, h = 5(p − 1 = 2^23 · 7 · 17、g の位数 = p − 1)1. 位数を求めるp−1 から q で削る2. 存在判定h^ord = 1 ?g^(ord/q^e), h^(ord/q^e)q^e = 2^2323 桁 × BSGS(位数 2)g^(ord/q^e), h^(ord/q^e)q^e = 71 桁 × BSGS(位数 7)g^(ord/q^e), h^(ord/q^e)q^e = 171 桁 × BSGS(位数 17)3. CRT で x mod ord → 109353319位数を p−1 と決めつけると、g が原始根でないとき最小解を外す(p<300 の全通りで 213432/1581937 誤答)各桁の探索は √q ≤ 317 要素。p = 10^18 でも BSGS の √p = 10^9 は要らない

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

ヒント1(方向性)

$n=\mathrm{ord}(g)=\prod q_i^{e_i}$ とする。$x\bmod q_i^{e_i}$ が全部わかれば CRT で $x\bmod n$ が決まる。$g_i=g^{n/q_i^{e_i}}$、$h_i=h^{n/q_i^{e_i}}$ とおくと、$g_i$ の位数は $q_i^{e_i}$ で、$g_i^{x}=h_i$。

ヒント2(アプローチ)

位数 $q^e$ の離散対数は、$x=d_0+d_1q+\dots+d_{e-1}q^{e-1}$($0\le d_k<q$)の桁ごとに解く。$\gamma=g_i^{q^{e-1}}$(位数 $q$)について、

$$\left(g_i^{-(d_0+\dots+d_{k-1}q^{k-1})}h_i\right)^{q^{e-1-k}}=\gamma^{d_k}$$

各桁は位数 $q\le10^5$ の離散対数なので、BSGS で $O(\sqrt q)$。

ヒント3(誘導)
order = p - 1
for q, e in fac:                     # p-1 の素因数分解
    for _ in range(e):
        if pow(g, order // q, p) == 1: order //= q
        else: break
if pow(h, order, p) != 1: return -1
# order の各 q^e について桁ごとに d_k を BSGS で求め、CRT

模範解答 (Python)

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


def sieve(n):
    s = bytearray([1]) * (n + 1)
    s[0] = s[1] = 0
    for i in range(2, isqrt(n) + 1):
        if s[i]:
            s[i * i::i] = bytearray(len(range(i * i, n + 1, i)))
    return [i for i in range(n + 1) if s[i]]


PRIMES = sieve(100000)


def bsgs(a, b, q, p):
    # 位数 q の元 a について a^x = b (0 <= x < q) を解く。必ず解がある前提
    m = isqrt(q) + 1
    table = {}
    e = 1
    for j in range(m):
        table.setdefault(e, j)
        e = e * a % p
    step = pow(a, -m, p)       # a^{-m}
    y = b
    for i in range(m):
        j = table.get(y)
        if j is not None:
            return i * m + j
        y = y * step % p
    raise ValueError("no log in subgroup")


def solve(p, g, h):
    g %= p
    h %= p
    if p == 2:
        return 0                # g = h = 1
    # p - 1 を素因数分解(素因数はすべて 1e5 以下の保証)
    n = p - 1
    fac = []
    for q in PRIMES:
        if q * q > n and n > 1 and n <= 100000:
            fac.append((n, 1))
            n = 1
        if n == 1:
            break
        if n % q == 0:
            e = 0
            while n % q == 0:
                n //= q
                e += 1
            fac.append((q, e))
    # g の位数 ord を p-1 から素因数を削って求める
    order = p - 1
    ofac = []
    for q, e in fac:
        for _ in range(e):
            if pow(g, order // q, p) == 1:
                order //= q
            else:
                break
        cnt = 0
        t = order
        while t % q == 0:
            t //= q
            cnt += 1
        if cnt:
            ofac.append((q, cnt))
    # 巡回群なので h が <g> に入る ⇔ h^ord = 1
    if pow(h, order, p) != 1:
        return -1
    # Pohlig–Hellman: 各 q^e について x mod q^e を q 進の桁ごとに求め、CRT でまとめる
    x_all, mod_all = 0, 1
    for q, e in ofac:
        qe = q ** e
        g0 = pow(g, order // qe, p)          # 位数 q^e
        h0 = pow(h, order // qe, p)
        gamma = pow(g0, q ** (e - 1), p)     # 位数 q
        ginv = pow(g0, -1, p)
        x = 0
        qk = 1
        for k in range(e):
            hk = pow(h0 * pow(ginv, x, p) % p, q ** (e - 1 - k), p)
            d = bsgs(gamma, hk, q, p)
            x += d * qk
            qk *= q
        # CRT: x_all mod mod_all と x mod qe を合わせる
        t = (x - x_all) * pow(mod_all, -1, qe) % qe
        x_all += mod_all * t
        mod_all *= qe
    return x_all % order if order > 1 else 0


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


main()

計算量: 素因数分解が $\pi(10^5)=9592$ 回の割り算、位数の計算が $O(\log p)$ 回の冪、本体が $\sum_i e_i(\sqrt{q_i}+\log p)$。$p\le10^{18}$ なら $\sum e_i\le60$、$\sqrt{q_i}\le317$。実測(Python 3.14.7、M1 Mac)で $T=100$、$p$ 最大 $5.7\times10^{17}$ が 0.08 s。

Step-by-Step 解説

1なぜ位数を先に求めるか

$g$ が原始根なら位数は $p-1$。そうでないと位数は $p-1$ の真の約数 $n$ で、解は $\bmod n$ で決まる。位数を $p-1$ と決めつけて同じ計算をすると、部分群に解が無いと誤判定するか、$\bmod(p-1)$ の別の代表元を出す。$p<300$ の全 1581937 通りで、位数を $p-1$ と決めつけた版は 213432 通り誤答(最小でない 139717、解があるのに -1 が 73715、実測)。最小の例は $p=7,g=2,h=2$ で、正解 1 に対し 4 を出す。

2存在判定 $h^n=1$

$\mathbb{Z}_p^*$ は巡回群なので、位数 $n$ の部分群はただ 1 つで、それは「$y^n=1$ を満たす $y$ 全体」に等しい。$\langle g\rangle$ はその部分群なので、$h\in\langle g\rangle\iff h^n=1$。合成数の法では群が巡回とは限らないので、この判定は使えない(Day168-Q5 の拡張 BSGS が要る理由の 1 つ)。

3位数 $q^e$ への射影

$g_i=g^{n/q^e}$、$h_i=h^{n/q^e}$ とすると、$g^x=h$ から $g_i^x=h_i$。$g_i$ の位数は $q^e$ なので、これは $x\bmod q^e$ だけを決める。

4桁ごとに解く

$x\bmod q^e=d_0+d_1q+\dots$。既に求めた桁の分 $g_i^{-(\cdots)}$ を $h_i$ に掛けて消し、$q^{e-1-k}$ 乗すると、残りの高い桁が消えて $\gamma^{d_k}$ だけが残る($\gamma$ の位数が $q$ なので)。各桁は位数 $q$ の BSGS。$q=2,e=23$($998244353$)のような場合も 23 回の小さな探索で済む。

5CRT と最小性

$x\bmod q_i^{e_i}$ を順に CRT で合わせると $x\bmod n$ が得られる。$g^x$ は $x\bmod n$ だけで決まるので、$[0,n)$ の代表元が最小の非負解。

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

案計算量採否理由
$x=0,1,\dots$ の反復$O(p)$検算用のみ$p<300$ の全 1581937 通りの照合に使った
BSGS を $\bmod p$ で直接$O(\sqrt p)$検算用のみ$p=10^{18}$ で $10^9$ 要素の表は不可。$p\le10^{10}$ の 100 件の照合に使った
Pohlig–Hellman(本解)$O(\sum e_i\sqrt{q_i})$採用小素数で全件一致、大きい $p$ で 100/100 が $g^x=h$ かつ $x<$ 位数
各桁を BSGS でなく Pollard の ρ$O(\sqrt q)$ 期待、メモリ $O(1)$不採用$q\le10^5$ では表が 317 要素で済み、メモリの利点が無い。乱択で検算が面倒
指数計算法(index calculus)準指数不採用$p-1$ が滑らかでない一般の $p$ 向け。本問の保証では過剰

前提が変わったら:

  • $p-1$ に $10^{12}$ 級の素因数があるなら、その部分の BSGS が $10^6$ 要素で済むうちはまだ使える。$10^{18}$ 級なら Pohlig–Hellman は効かず、指数計算法の領域(本セッションでは扱わない)
  • $p-1$ の素因数分解が与えられない一般の $p$ なら、Pollard の ρ(素因数分解)を先に入れる
  • 法が合成数なら、群が巡回とは限らず、存在判定 $h^n=1$ が使えない。Day168-Q5 の拡張 BSGS か、法を素数冪に分けて別々に解く

よくあるミス

ミス原因正しい書き方
位数を $p-1$ と決めつける$g$ を原始根だと思い込む位数を素因数で削って求める(213432/1581937 誤答)
存在判定を省き、BSGS が見つけられないとき例外で落ちる部分群に解があると決めつける先に $h^n=1$ を確かめる
桁ごとの補正で $g_i^{-x}$ ではなく $g^{-x}$ を掛ける射影前と後の元の取り違え射影後の $g_i$ の逆元 ginv
CRT の後に $\bmod n$ を取らないCRT の結果は $[0,n)$ だが、$n=1$ の扱いを忘れるx_all % order、order == 1 なら 0
$p=2$ を特別扱いしない$p-1=1$ で素因数が無い$g=h=1$ なので 0

次のステップ

  • 発展1: $p-1$ の素因数分解を Pollard の ρ で行い、「最大の素因数が $10^{10}$ 以下」まで広げる
  • 発展2: 楕円曲線上の離散対数に Pohlig–Hellman を当て、曲線の位数が滑らかだと危険な理由を確かめる
  • 発展3: Pohlig・Hellman(1978)の原典を読む(本セッションでは未確認)

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

  • 反復との照合(実測): $p<300$ のすべての素数と、すべての $1\le g,h<p$ の全 1581937 通りで、$x=0,1,\dots,p-1$ の反復と一致(うち -1 は 652467 通り)
  • 大きい $p$(実測): $p-1$ が $10^5$ 以下の素因数だけになる素数を乱択で作り、$p\le10^{18}$(最大 566228048925458921)の 100 件で、出力 $x$ が $g^x\equiv h$ かつ $0\le x<$ 位数(位数は $p-1$ の全約数から最小のものを別経路で計算)を満たす。-1 の 18 件は $h^{\text{位数}}\ne1$ を確認
  • BSGS との照合(実測): $p\le10^{10}$ の 100 件で、$\bmod p$ の素朴な BSGS(最小解)と全件一致
  • 誤った方法(実測): 位数を $p-1$ と決めつけると 1581937 通り中 213432 通り誤答(最小でない 139717、-1 と誤る 73715、存在しないのに答えを出す 0)
  • 入力例(実測): 4 -1 1 109353319 -1。位数を決めつけた版は 3 つ目で 4 を出す
  • 実行時間(実測): $T=100$、$p\le10^{18}$ で 0.08 s
  • 扱っていない範囲: $p-1$ に $10^5$ を超える素因数がある入力(保証外。本解は誤った答えを出しうる)、Pollard の ρ・指数計算法、楕円曲線版、原典の確認

自己評価

自分の回答

気づき・メモ