Day 062-Q4 — Miller-Rabin + Pollard's ρ連鎖(素因数分解・radical $N \le 10^{18}$)

2026-06-15 赤色 Master / Phase 8+ ★★★★★★★★★ Miller-Rabin / Pollard's ρ / 最大素因数 / radical

問題

$T$ 個の整数 $N_i$($N_i \le 10^{18}$)が与えられる。各 $N_i$ について:

  1. 素因数分解の結果(各素因数と指数)
  2. 最大素因数
  3. radical(相異なる素因数の積 = $\prod_{p | N} p$)

を出力せよ。

制約

パラメータ範囲
$T$$1 \le T \le 100$
$N_i$$1 \le N_i \le 10^{18}$

入出力例

入力例 1

3
12
998244353
123456789012345678

出力例 1

12: {2: 2, 3: 1} max=3 rad=6
998244353: {998244353: 1} max=998244353 rad=998244353
123456789012345678: {2: 1, 3: 2, 7: 1, ...} max=... rad=...

$998244353$ は素数(NTT でよく使われるもの)。$12 = 2^2 \cdot 3$、radical = $2 \cdot 3 = 6$。

概念図: Miller-Rabin と Pollard's ρ のフロー

Miller-Rabin 素数判定 1. $n-1 = 2^r \cdot d$ と分解 2. $x = a^d \bmod n$ を計算 3. $x = 1$ or $x = n-1$ → 合成数でない 4. $r-1$ 回 $x = x^2 \bmod n$ 途中 $x = n-1$ → 合成数でない 5. ここまで来たら合成数 底集合: {2,3,5,7,11,13,17,19,23,29,31,37} → N ≤ 3.3×10²⁴ で決定論的に正確 計算量: O(log² n) per base 全体: O(12 × log² n) Pollard's ρ 因数分解 $f(x) = x^2 + c \bmod n$ の擬似乱数列 フロイドのサイクル検出: $x \leftarrow f(x)$(亀) $y \leftarrow f(f(y))$(兎) $d = \gcd(|x-y|, n)$ $d = 1$: 継続 $d = n$: $c$ を変えて再試行 $1 < d < n$: 非自明因子発見! 再帰的に因数分解: factorize(d) ∪ factorize(n/d) 素数判定 → Miller-Rabin で確認 期待計算量: $O(n^{1/4} \log n)$

ヒント(段階的開示)

ヒント1: 方向性
$N \le 10^{18}$ の素因数分解は Miller-Rabin(確率的素数判定)+ Pollard's ρ(乱択因数分解)の組み合わせ。素数判定で素数なら終了、合成数なら ρ で因子を見つけて再帰。
ヒント2: アプローチ
  • Miller-Rabin: 底 $\{2,3,...,37\}$ の12個で判定。$N \le 3.3 \times 10^{24}$ で確定的に正確
  • Pollard's ρ: $f(x) = x^2 + c \bmod n$ でフロイドサイクル検出、$\gcd$ で因子抽出
  • 再帰: 発見した因子 $d$ と $n/d$ を再帰的に素因数分解してマージ
ヒント3: コード骨格
def is_prime(n):
    for a in [2,3,5,7,11,13,17,19,23,29,31,37]:
        if n == a: return True
        if n % a == 0: return False
        if not miller_rabin(n, a): return False
    return True

def factorize(n):
    if n <= 1: return {}
    if is_prime(n): return {n: 1}
    d = pollard_rho(n)
    f1 = factorize(d); f2 = factorize(n // d)
    for k,v in f2.items(): f1[k] = f1.get(k,0) + v
    return f1

模範解答 (Python)

import sys
from math import gcd
from random import randint, seed
from collections import defaultdict
input = sys.stdin.readline

seed(42)

def miller_rabin(n, a):
    if n % a == 0:
        return n == a
    d, r = n - 1, 0
    while d % 2 == 0:
        d //= 2; r += 1
    x = pow(a, d, n)
    if x == 1 or x == n - 1:
        return True
    for _ in range(r - 1):
        x = x * x % n
        if x == n - 1:
            return True
    return False

BASES = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]

def is_prime(n):
    if n < 2: return False
    for a in BASES:
        if n == a: return True
        if n % a == 0: return False
    for a in BASES:
        if not miller_rabin(n, a): return False
    return True

def pollard_rho(n):
    if n % 2 == 0: return 2
    while True:
        x = randint(2, n - 1)
        y = x; c = randint(1, n - 1); d = 1
        while d == 1:
            x = (x * x + c) % n
            y = (y * y + c) % n
            y = (y * y + c) % n
            d = gcd(abs(x - y), n)
        if d != n: return d

def factorize(n):
    if n <= 1: return defaultdict(int)
    if is_prime(n):
        res = defaultdict(int); res[n] += 1; return res
    d = pollard_rho(n)
    f1 = factorize(d); f2 = factorize(n // d)
    for k, v in f2.items(): f1[k] += v
    return f1

def main():
    T = int(input())
    for _ in range(T):
        n = int(input())
        if n == 1:
            print("1: {} max=1 rad=1"); continue
        factors = factorize(n)
        sorted_f = sorted(factors.items())
        max_p = max(factors.keys())
        rad = 1
        for p in factors: rad *= p
        fstr = "{" + ", ".join(f"{p}: {e}" for p,e in sorted_f) + "}"
        print(f"{n}: {fstr} max={max_p} rad={rad}")

main()

Step-by-Step 解説

Step 1: Miller-Rabin 素数判定

$n-1 = 2^r \cdot d$ と分解し、$a^d \bmod n$ から強擬素数テストを実施。12個の底で $n \le 3.3 \times 10^{24}$ まで確定的に正確。計算量 $O(\log^2 n)$(各底ごと)。

Step 2: Pollard's ρ 因数分解

$f(x) = x^2 + c \bmod n$ の擬似乱数列でフロイドのサイクル検出。$\gcd(|x-y|, n) \ne 1, n$ で非自明因子を発見。$c$ を変えて再試行することで確率1で成功。期待計算量 $O(n^{1/4} \log n)$。

Step 3: 再帰的分解とマージ

発見した因子 $d$ と $n/d$ を再帰的に factorize し、辞書をマージ。最終的に素数の指数辞書が完成する。radical は相異なる素因数の積。

よくあるミス

ミス原因正しい書き方
底の比較を忘れるn == a チェックなし各底 $a$ について if n == a: return True を先に
ρ が d==n に無限ループc の固定while Truec を再乱択
$n=1$ の特殊処理なしfactorize(1) が空辞書if n <= 1: return {}

次のステップ

  • 発展問題: ECM(楕円曲線法)・General Number Field Sieve(現代の巨大数因数分解)
  • 関連: RSA 暗号の安全性・素数生成・暗号プロトコルの実装

自己評価