0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Sympyを使ってn次合同方程式を解く

Last updated at Posted at 2022-10-25

【例題】N=500のとき以下のf(x)が911で割り切れるようなxの合計を求めよ

\Large f(x) = x^{13} + 1  \ \ \ \  (1 \le x \le N)

素朴にループで解く

もちろんN=500程度であればこのような単純ループで解くことが出来ますがNが大きくなると$f(x)$の値が大きくなり効率がよくありません。そこでタイトルのようにn次合同方程式を解くことを考えます。

ans = [x for x in range(1,500+1) if (x**13+1)%911 == 0]
print(f"Answer: {sum(ans)} ({ans})")
# Answer: 1606 ([11, 14, 330, 334, 420, 497])

n次合同方程式を解く

はっきり言ってn次合同方程式を解くのは簡単ではありませんが、こちらの動画を何度も見てなんとかやってみました。

【初等整数論44】11x≡5 (mod 13) や x^3≡5 (mod 13) をIndの力で解く!【数学 整数論】(みつのきチャンネル)

\begin{align}
&まずf(x)が911で割り切れるので \\
&x^{13} + 1  \equiv 0 \pmod{911} \\
&移項して正の数に変えます \\
&x^{13} \equiv -1 = 910 \pmod{911} \cdots (1)\\
\end{align}

次に必要となる911の原始根の一つ17をsympyのprimitive_rootで求めます。
また後で必要になる$Ind_{17}910$をdiscrete_logを使って求めておきます。

from sympy.ntheory import primitive_root, discrete_log
p = 911
g = primitive_root(p)
dl = discrete_log(p,p-1,g)
print(f"p = {p}, primitive_root ={g}, Ind({g},{p-1})={dl}")
# p = 911, primitive_root =17, Ind(17,910)=455
\begin{align}
&原始根が17なので式(1)の両辺のInd_{17}を取ります \\
&このときmodはp-1の910になります \\
&Ind_{17}x^{13} \equiv  Ind_{17}910 \pmod{910}  \\
&13が前に出て、Ind_{17}910=455なので\\
&13 \times Ind_{17}x \equiv  455 \pmod{910} \\
&両辺を13で割ります(modも含めて)ここで解が13個あることが分かります。\\
&Ind_{17}x \equiv  35 \pmod{70} \\
&以下の13個です\\
&Ind_{17}x = (35, 105, 175, 245, 315, 385, 455, 525, 595, 665, 735, 805, 875) \\
&これらをaとするとx=17^a \pmod {911}なので \\
&x = (11, 14, 330, 334, 420, 497, 715, 757, 783, 790, 846, 881, 910) \\
&となります\\
\end{align}

従って例題の答えはこの$x$の500以下のもの$(11, 14, 330, 334, 420, 497)$となりループでの解答と一致しました。

n次合同方程式を解く汎用関数rootn_mod

このアルゴリズムを汎用化して$x^m \equiv a \pmod p$を解く関数rootn_modを作りました。これを使って例題を解くと当然同じ答えになります。

from sympy.ntheory import primitive_root, discrete_log
from sympy import mod_inverse
import math

def rootn_mod(a,m,p):     # Return x array for x**m = a (mod p)
  g = primitive_root(p)       # primitive root of p
  Inda = discrete_log(p,a,g)  # Ind (a,g) mod p
  d = math.gcd(m, p-1)        # d: number of solutions
  if Inda%d != 0: return []   # No solution
  if Inda == 0: return [1]    # x=1
  m //= d; Inda //=d; p1 = (p-1)//d   # divid both sides by d
  Inda = (Inda*mod_inverse(m, p1))%p1    # ax = b -> x = b * mod_inverse(a)
  return sorted([pow(g,a,p) for a in range(Inda,p,p1)])

m, p, N = 13, 911, 500
ans = [a for a in rootn_mod(p-1,m,p) if a <= N]
print(f"m={m}, p={p}, answer={ans}")
# m=13, p=911, answer=1606([11, 14, 330, 334, 420, 497])

さらにこのアルゴリズムが有効である証として$N=10^{10}$として例題を解いてみます。

【例題 2】N=10**10として例題 1を解け

これだけNが大きくなるとループでは不可能になりますが、n次合同方程式を解く形では以下のようにして解くことができます。

\begin{align}
&例えば例題 1の解の一つが11のとき解はmod\ 911で同じなら解なので \\
&x = 11 + 911 \times y (x \le N)\\
&も解になります。\\
\end{align}

これらの解の和は等差数列の和の公式から求まるので、その関数get_sum

def get_sum(a, b, d):  # return a+(a+d)+(a+2d)+,,,  < b
    if a > b: return 0
    n = (b - a)//d + 1
    return n*(2*a+(n-1)*d)//2

となります。これを既に求まった解すべてに適用すればよいので

m, p, N = 13, 911, 10**10
print(sum([get_sum(a, N, p) for a in rootn_mod(p-1,m,p)]))
# 713501648457738379

と簡単に求まりました。

Sympyのnthroot_mod

ちなみにSympyにはrootn_modと同等の関数nthroot_modが既に存在してそれは使えば同様に簡単に書く事が出来ました。
ただn次合同方程式の解き方を勉強するためにアルゴリズムを理解することは重要だと考えたのでこの記事を書いてみました。

from sympy.ntheory import nthroot_mod
m, p, N = 13, 911, 10**10
print(sum([get_sum(a, N, p) for a in nthroot_mod(p-1,m,p, True)])) 

(開発環境:Google Colab)

このアルゴリズムはProject Euler, Problem 421: Prime factors of n15+1を解くのに役に立ちます。

0
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?