Day 089-Q4 — Pell方程式(連分数展開による基本解探索)

2026-07-12 赤色 Master / Phase 8+ ★★★★★★★★★ Pell方程式・連分数・数論

問題

平方数でない正整数 $D$ が与えられる。次の方程式(Pell方程式)

$$x^2 - D y^2 = 1$$

を満たす正整数の組 $(x,y)$ のうち、$x$ が最小となるもの(基本解)を求めよ。

制約

パラメータ範囲備考
$D$$2 \le D \le 10^6$平方数でないことが保証される

入出力例

入力例1

2

出力例1

3 2

入力例2

61

出力例2

1766319049 226153980

例1: $3^2-2\times2^2=1$。例2: $D=61$ は基本解が異常に大きくなることで有名(フェルマーがウォリスに出した問題)。

概念図: $\sqrt D$ の連分数展開から収束分数を生成し $h_i^2-Dk_i^2=1$ を探す

$\sqrt D = [a_0;\overline{a_1,a_2,\dots,a_r}]$(循環連分数) a0 a1 a2 ... ar 各 $a_i$ ごとに収束分数 $h_i/k_i$ を更新 最初に $h_i^2-Dk_i^2=1$ となる $(h_i,k_i)$ が基本解

ヒント

ヒント1(方向性)

$y=1,2,3,\dots$ と全探索して $Dy^2+1$ が平方数になるか調べる方法は、$D=61$ のように基本解が巨大になるケースで全く間に合わない。$\sqrt D$ の連分数展開を使う古典的な理論が必要。

ヒント2(アプローチ)

$\sqrt D$ を連分数展開 $[a_0;\overline{a_1,\dots,a_r}]$(循環連分数)すると、その収束分数 $h_i/k_i$ が Pell方程式の解候補になる。$h_i^2-Dk_i^2=\pm1$ が交互に現れ、$+1$ になる最初の収束分数が基本解を与える。

ヒント3(ほぼ答え)
m, d, a = 0, 1, a0
h_prev2, h_prev1 = 1, a0
k_prev2, k_prev1 = 0, 1
while True:
    m = d*a - m
    d = (D - m*m)//d
    a = (a0+m)//d
    h = a*h_prev1 + h_prev2
    k = a*k_prev1 + k_prev2
    if h*h - D*k*k == 1:
        return h, k
    h_prev2, h_prev1 = h_prev1, h
    k_prev2, k_prev1 = k_prev1, k

模範解答

import sys, math

def main():
    D = int(sys.stdin.readline())
    a0 = math.isqrt(D)

    m, d, a = 0, 1, a0
    h_prev2, h_prev1 = 1, a0
    k_prev2, k_prev1 = 0, 1

    if h_prev1 * h_prev1 - D * k_prev1 * k_prev1 == 1:
        print(h_prev1, k_prev1)
        return

    while True:
        m = d * a - m
        d = (D - m * m) // d
        a = (a0 + m) // d
        h = a * h_prev1 + h_prev2
        k = a * k_prev1 + k_prev2
        if h * h - D * k * k == 1:
            print(h, k)
            return
        h_prev2, h_prev1 = h_prev1, h
        k_prev2, k_prev1 = k_prev1, k

main()

計算量: 連分数の周期長は $D$ に対し経験的に十分小さく、各ステップは多倍長整数の定数回演算。$D\le10^6$ なら実用上一瞬で終わる。

Step-by-Step 解説

Step 1: $\sqrt D$ の連分数展開を漸化式で計算

$m_i,d_i,a_i$ の三つ組で $\sqrt D=[a_0;a_1,a_2,\dots]$ を1項ずつ計算する。この展開は必ず周期的になる。

Step 2: 収束分数を同時に更新

$h_i=a_ih_{i-1}+h_{i-2}$、$k_i=a_ik_{i-1}+k_{i-2}$ で $\sqrt D$ の有理数近似を更新する。

Step 3: $h_i^2-Dk_i^2=1$ を毎回チェック

周期の偶奇を意識せず、最初に条件を満たした $(h_i,k_i)$ を採用すれば正しく基本解が求まる。

Step 4: 多倍長整数でそのまま扱う

基本解が10桁を超えることも珍しくないが、Pythonの整数は任意精度なので問題ない。

よくあるミス

ミス原因正しい書き方
$y$ を1から全探索基本解が巨大になるケースを想定していない連分数展開のアルゴリズムを使う
$h_i^2-Dk_i^2=-1$ を解と誤判定収束分数は $\pm1$ を交互に取ることを見落とす必ず==1を明示的にチェック
浮動小数点で $\sqrt D$ を計算し誤差蓄積math.sqrtの精度不足math.isqrtで整数演算にとどめる

次のステップ

  • 発展問題: $x^2-Dy^2=-1$ の解の存在判定(周期が奇数長のときのみ解が存在)
  • 発展問題: 一般化Pell方程式 $x^2-Dy^2=N$ への拡張(LMM法・連分数併用)

自己評価

理解度: / /

自分の回答:

気づき・メモ: