0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ヘンゼルの補題を使って法がp**kの合同方程式を解く

Last updated at Posted at 2022-11-10

[例題 1] 以下の合同方程式を解け

\begin{align}
&f(x) = x^3+8x-5のとき\\
&f(x) \equiv 0 \pmod{5^5} \\
&となるxを求めよ
\end{align}

このような問題を解くのに役に立つのがヘンゼルの補題(Wikipedia)です。このWikiの説明はかなり難解ですが、ぶっちゃけて言うと。

  • $f(x) \equiv 0 \pmod{p}\ (pは素数)$の解が見つかれば$\pmod{p^k}$での解を逐次的に計算するアルゴリズムがある(条件あり)ということです。

今回の投稿では$\pmod{p^k}$の答えから$\pmod{p^{k+1}}$を求める「ヘンゼル持ち上げ」(hensel lift)のアルゴリズムをpythonで実装して、例題を解いてみたいと思います。

ヘンゼル持ち上げ(hensel lift)

\begin{align}
&f(x) \equiv 0 \pmod{p}\ (pは素数) の解をx_1とする \\

&k番目の式、f(x_k) \equiv 0 \pmod{p^{k}} が成り立つとき \\
&x_{k+1} \equiv x_k \pmod{p} となるので(ヘンゼルの補題より)\\
&x_{k+1} = x_k+p^ktと置ける \cdots (1)\\
&0 \equiv f(x_{k+1}) = f(x_k+p^kt) \\
&右辺をテイラー展開すると \\
&= f(x_k)+p^ktf'(x_k) + (p^kt)^2f''(x_k)/2!+... \\
&この3項目以降はp^{k+1}で割ると0になるので\\
& f(x_k)+p^ktf'(x_k) \equiv 0 \pmod {p{k+1}}  \\
\end{align}
\begin{align}
&f(x_k) はp^kで割れるので、両辺をp^kで割る \\
&\frac{f(x_k)}{p^k}+tf'(x_k) \equiv 0 \pmod {p} \\
&x_k \equiv x_{k-1} ... \equiv x_1 \pmod {p} より f'(x_k) \equiv f'(x_1) \pmod {p}\\
&ここで a= modinv(f'(x1)) \pmod pと置くとtが求まり \\
&t \equiv -a \frac{f(x_k)}{p^k} \\
&これを(1)に代入して \\
&x_{k+1} = x_k + p^kt = x_k - af(x_k) \pmod p^{k+1} \\
&以下の漸化式が求まります\\ 
\end{align}
\begin{array}{|c|} \hline \\ 
【ヘンゼル持ち上げの漸化式】 \\
\large \ \ \ \  x_{k+1} = x_k - af(x_k) \pmod p^{k+1} \ \ \ \ \ \\ 
ただし \ a = modinv(f'(x1)) \pmod p  \\ 
\\ \hline
\end{array}

ヘンゼル持ち上げの実装(その1)

$modinv$の所をsympyのmod_inverseを使うと$x_k$から$x_{k+1}$を求める関数hl_stepはそのままの実装になります。$a=modinv(f'(x)) \pmod p$は一度計算すればよいの先に求めておきます。

from sympy import mod_inverse
def hl_step(x, a, k):
  return (x - a*f(x))%p**k

p, x, x1, f(x), f1(x) = ...
a = mod_inverse(f1(x1),p)

例題では、$x_1=0$なので$k=5$までループして答えの$x_5 = 1985$が得られます。各々の$f(x_k) \equiv 0 \pmod {p^k}$となっていることも確認できました。

def f(x): return x**3 + 8*x - 5
def f1(x): return 3*x**2 + 8
def hl_step(x, a, k):
  return (x - a*f(x))%p**k
p, x, x1 = 5, 0, 0
a = mod_inverse(f1(x1),p)
for k in range(2,5+1):
  x = hl_step(x, a, k)
  print(f"k={k}, x={x}, f({x})%{p}**{k}={f(x)%p**k}")
# k=2, x=10, f(10)%5**2=0
# k=3, x=110, f(110)%5**3=0
# k=4, x=110, f(110)%5**4=0
# k=5, x=1985, f(1985)%5**5=0

ヘンゼル持ち上げの実装(複数解対応)

【例題 1】は$x_1$の解が1つでしたが、三次方程式なら最大3つあるので、そのような複数解に対応するようなプログラムを実装します。

【例題 2】以下の合同方程式を解け

\begin{align}
&f(x) = x^3+3x-7のとき\\
&f(x) \equiv 0 \pmod{7^5} \\
&となるxをすべて求めよ
\end{align}

まず$x_1 = f(x) \equiv 0 \pmod{7}$の解ですが、以下のように$[0, 2, 5]$の3つあります。

def f(x): return x**3 + 3*x - 7
p = 7
x1list = [i for i in range(p) if f(i) % p == 0]
print(f"p = {p}, x1={x1list}")
# p = 7, x1=[0, 2, 5]

従って$x_1$のリストの各々に対してhl_stepを再帰的に呼びだすように実装しました。
【例題 2】の解は$[3318, 3061, 11275]$になります。

from sympy import mod_inverse

def hensel(f, f1, x1list, p, k):
  #--- hensel lift step ---
  def hl_step(x, a, k):
    return (x - a*f(x))%p**k

  def _hl(p, k): # return [(x1n, a),(x2n,a),...]
    if k == 1: return  [(i, mod_inverse(f1(i),p)) for i in x1list]
    xl = _hl(p,k-1)  
    return [(hl_step(x, a, k), a) for (x, a) in xl ]
  return [x for (x, a) in _hl(p,k)]

def f(x): return x**3 + 3*x - 7
def f1(x): return 3*x + 3
p = 7
x1list = [i for i in range(p) if f(i) % p == 0]
print(f"p = {p}, x1={x1list}")
for k in range(2,5+1):
  hl = hensel(f, f1, x1list, p, k)
  print(k, hl)
# p = 7, x1=[0, 2, 5]
# 2 [35, 23, 33]
# 3 [231, 156, 5]
# 4 [917, 1374, 2140]
# 5 [3318, 3061, 11275]

この考え方はEuler Project: Problem 457: A polynomial modulo the square of a primeと解くのに役に立ちます。

(開発環境:Google Colab)

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?