Day 165-Q5 — 2026-09-29 | 最小の原始根($p\le10^{12}$ の素数。$p-1$ の素因数ごとの位数判定)

2026-09-29 赤色 Master (Phase 8+) ★★★★★★★★★ 数論(焦点帯 M3)。$g$ が原始根であるための必要十分条件「$p-1$ の各素因数 $q$ で $g^{(p-1)/q}\ne1$」を使い、$p-1$ を $10^6$ までの試し割りで素因数分解して $g=2,3,\dots$ を順に試す。落とし穴は平方非剰余の判定(オイラーの規準)だけで済ませること

問題

素数 $p$ が $T$ 個与えられる。各 $p$ について、最小の原始根($\mathbb{Z}/p\mathbb{Z}$ の乗法群の生成元のうち最小の正整数)を求めよ。$p=2$ のときの答えは 1 とする。

入力形式

T
p_1
...
p_T

制約

#条件
1$1\le T\le 100$
2$2\le p_i\le10^{12}$、$p_i$ は素数
3入力はすべて整数

入出力例

入力例1

3
2
7
998244353

出力例1

1
3
3

(7 の位数: $3^1=3,3^2=2,3^3=6,3^6=1$ で位数 6。$2$ は $2^3=1$ で位数 3 なので原始根でない)

背景と出典

「$g$ が原始根 $\Leftrightarrow$ $p-1$ の各素因数 $q$ について $g^{(p-1)/q}\not\equiv1$」は初等整数論の標準事項(位数が $p-1$ の約数であることから従う)。最小原始根の大きさの上界(Burgess の評価で $O(p^{1/4+\varepsilon})$ 程度と私は記憶している)と、「小さい原始根はすぐ見つかる」という経験則(Artin の予想に関連)は本セッションでは原典を開いておらず未確認。

実測で確かめたのは、$p<10^5$ の素数での最小原始根の最大が 44($p=71761$)という事実と、$10^{11}$ から $10^{12}$ までの 51 個の素数で最小原始根の最大が 29 だったこと(検証ログ)。最小原始根の上界は示していない。

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

ヒント1(方向性)

$g$ の位数が $p-1$ であることを調べたい。位数は $p-1$ の約数なので、「位数が $p-1$ の真の約数ではない」ことを言えればよい。真の約数は、$p-1$ を素数 $q$ で割ったものの約数。

ヒント2(アプローチ)

$p-1$ の異なる素因数を $q_1,\dots,q_r$ とする。$g$ が原始根であることは、すべての $i$ で $g^{(p-1)/q_i}\not\equiv1\pmod p$ と同値。$g=2,3,4,\dots$ と順に調べ、最初に成立した $g$ が答え。

ヒント3(誘導)

$p-1\le10^{12}$ の素因数分解は、$10^6$ までの素数で試し割りすれば足りる($\sqrt{10^{12}}=10^6$)。割り切れたら完全に割る。最後に 1 でない残りが大きな素因数。篩は最初に 1 回作る。

---

模範解答 (Python)

模範解答を表示する(まず自力で解いてから)
import sys
def factor_primes(x, primes):
    """x の異なる素因数。primes は sqrt(x) 以上まで"""
    fs = []
    for q in primes:
        if q * q > x: break
        if x % q == 0:
            fs.append(q)
            while x % q == 0: x //= q
    if x > 1: fs.append(x)
    return fs
def main():
    data = sys.stdin.read().split()
    t = int(data[0]); ps = [int(x) for x in data[1:1 + t]]
    lim = 10**6 + 1
    sv = bytearray([1]) * (lim + 1); sv[0:2] = b"\x00\x00"
    for i in range(2, int(lim ** 0.5) + 1):
        if sv[i]: sv[i * i::i] = bytearray(len(sv[i * i::i]))
    primes = [i for i in range(2, lim + 1) if sv[i]]
    out = []
    for p in ps:
        if p == 2: out.append(1); continue
        qs = factor_primes(p - 1, primes)
        g = 2
        while True:
            # g が原始根 <=> 各素因数 q で g^((p-1)/q) != 1 (mod p)
            if all(pow(g, (p - 1) // q, p) != 1 for q in qs): break
            g += 1
        out.append(g)
    print("\n".join(map(str, out)))
main()

計算量: 篩 $O(L\log\log L)$($L=10^6$)、各 $p$ の素因数分解 $O(\pi(\sqrt p))\approx7.8\times10^4$ 回以下、判定は $O(r\log p)$ を最小原始根 $g$ 回。実測(Python 3.14.7、M1 Mac)で、$10^{11}\sim10^{12}$ の素数 51 個($10^{12}$ 未満で最大の素数 999999999989 を含む)が 0.10 s。

---

Step-by-Step 解説

1位数の条件

$g^{p-1}\equiv1$(フェルマーの小定理)なので、$g$ の位数は $p-1$ の約数。位数が $p-1$ より小さければ、$p-1$ のある素因数 $q$ について位数は $(p-1)/q$ の約数になり、$g^{(p-1)/q}\equiv1$。逆にそうでなければ位数は $p-1$ に等しい。これが判定条件。

2$p-1$ の素因数分解

$p\le10^{12}$ なので $p-1$ の素因数は $10^6$ 以下の素数で試し割りし、残った $x>1$ は素数($x$ が合成数ならその最小の素因数が $\sqrt x\le10^6$ 以下)。$x$ が小さくなるたびに q * q > x で打ち切る。

3小さい $g$ から試す

$g=2$ から順に判定する。原始根は $\varphi(p-1)$ 個あり、その割合は $\varphi(p-1)/(p-1)$。この割合が小さくなるのは $p-1$ が多くの小さい素数で割れるとき。実測では $p<10^5$ で最小原始根の最大は 44、大きい素数 51 個では 29 以下。上界は示していない。

4$p=2$ と $p=3$

$p=2$ は乗法群が自明($\{1\}$)で、答えを 1 とする。$p=3$ は $p-1=2$、素因数 $\{2\}$ で、$g=2$($2^1=2\ne1$)が原始根。

5個数の公式を使う

原始根の個数は $\varphi(p-1)$(巡回群の生成元の個数)。本問では個数を出さないが、全探索の照合で最初の 80 素数($p\le409$)の個数と一致することを確認した。

---

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

案計算量採否理由
$p-1$ の各素因数 $q$ で $g^{(p-1)/q}\ne1$ を調べる(本解)$O(\pi(\sqrt p)+rg\log p)$採用全探索と 430 素数($p<3000$)全件一致、$10^{12}$ 近くの 51 個を独立に再検証して一致
$g$ の位数を $p-1$ 回掛けて直接調べる$O(p)$検算用のみ$p<3000$ でしか動かない
オイラーの規準 $g^{(p-1)/2}\ne1$ だけ$O(\log p)$不採用平方非剰余の判定で、原始根の必要条件にすぎない。430 素数中 104 個で最小値が違う
Pollard rho で素因数分解$O(p^{1/4})$不採用$p\le10^{12}$ は試し割りで十分速い。$p\le10^{18}$ に拡張するときに必要
篩を $p$ ごとに作り直す$O(T\cdot L)$不採用最初に 1 回作る

前提が変わったら:

  • $p\le10^{18}$ なら試し割りは届かない。Miller–Rabin と Pollard の rho 法で $p-1$ を素因数分解する($p$ が素数であるという入力の保証も、判定が必要になる)
  • 合成数の法($n$ が $1,2,4,p^k,2p^k$ の形のときだけ原始根がある)なら、位数の指数を $\varphi(n)$ に取り替える
  • すべての原始根を列挙するなら、1 つ見つけて $g^k$($\gcd(k,p-1)=1$)を並べる

---

よくあるミス

ミス原因正しい書き方
$g^{(p-1)/2}\ne1$ だけで判定平方非剰余と原始根を混同する全素因数で判定(104/430 誤答)
$p-1$ の素因数を重複して持つ指数まで別々の素因数として扱う異なる素因数だけ(while x % q == 0 で割り切る)
篩を $\sqrt p$ ではなく $p$ までにする$p-1$ の素因数が大きいと勘違い$10^6$ までで足りる(残りは素数)
$p=2$ を特別扱いしない$p-1=1$ は素因数が無く条件が空で真になり、答え 1 ではなく 2 を返す特別扱いで 1 を出す

次のステップ

発展1: $p\le10^{18}$ に拡張する(Miller–Rabin + Pollard の rho 法)

  • 発展2: 原始根をすべて列挙し、離散対数 $\log_g x$ を BSGS で求める
  • 次回予告: 焦点帯 M1 から 3 問、M2・M3 から 1 問ずつ

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

  • 全探索との照合(実測): $2\le p<3000$ の素数 430 個で、位数を直接数える全探索の最小原始根とプログラムの出力が全件一致。最初の 80 素数($p\le409$)で、原始根の個数が $\varphi(p-1)$ と一致(不一致 0)
  • 大きい入力の独立再検証(実測): $10^{11}\le p\le10^{12}$ の素数 50 個と 999999999989($10^{12}$ 未満で最大の素数)の計 51 個で、答え $g$ が原始根であり、$1..g-1$ に原始根が無いことを別の素因数分解(素朴な試し割り)で確認、不一致 0。最小原始根の最大は 29
  • 誤った方法(実測): オイラーの規準だけ($g^{(p-1)/2}\ne1$)は 430 素数中 104 個で最小値が異なる
  • 最小原始根の最大(実測): $p<10^5$ で 44($p=71761$)
  • 入力例(実測): 例 1 は 1, 3, 3(出力例どおり)
  • 実行時間(実測): 上の 51 個で 0.10 s
  • 扱っていない範囲: $p\ge10^{12}$、合成数の法、最小原始根の上界の証明、Burgess の評価と Artin の予想の原典確認

自己評価

自分の回答

気づき・メモ