1
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.

Problem 95: 社交数(3個以上の友愛数)

Last updated at Posted at 2022-03-07
  • 本記事はProjectEulerの「100番以下の問題の説明は記載可能」という規定に基づいて回答のヒントが書かれていますので、自分である程度考えてみてから読まれることをお勧めします。

問題 95. 社交数(3個以上の友愛数)

原文 Problem 95: Amicable chains

問題の要約:すべての要素が$10^6$以下の最長の社交数ループの要素の最小値を求めよ

社交数(Wikipedia)とはあまりなじみのない言葉ですがProblem 21: 友愛数で出てきた友愛数の3個以上のものを言うようです。

従ってまずそこで作った自分自身を除いた約数(proper divisors)の和」を求める関数sumpdivを使って$10^6$以下の整数の約数の和を配列に格納します。

その配列からループになっている友愛数のチェーンを探す関数amiChainを使って探し長さが最大の物を見つけます。

これでも1分強で終了するので、まあOKかなと思いますが。やはりさらなる高速化に挑戦します。

import sympy
def sumpdiv(n):
  return sum(sympy.divisors(n))-n

def amiChain(spd, i):         # Create a chain
  chain = []
  while i not in chain:       
    if i > N:  return []      # out of range
    chain, i = chain+[i], spd[i]
  return chain[chain.index(i):]

N = 10**6
spd = [sumpdiv(n) for n in range(0,N)]
#print(spd[:20])

maxc = 1 
for i in range(len(spd)):
  chain = amiChain(spd, i)
  if len(chain) > maxc:
    maxc, maxchain = len(chain), chain

print(min(maxchain), len(maxchain), maxchain)

約数関数の公式を使った高速化

約数関数(Wikipedia)によると約数関数$\sigma (n)$の公式はいくつかあるみたいですが以下の2つを使います。(ここでは$ \sigma(n)=\sigma_1(n)$とします)

\large 乗法定理 \sigma(ab)=\sigma(a) \times \sigma(b)\ \ \ \ \  if \ gcd(a,b) = 1  \cdots (1)\\
\large n = p^k とすると \sigma(p^k)=1+p+p^2+\cdots +p^k  \cdots (2)\\ \\

まず整数$n$に含まれる素数$p$から$(2)$を使って$\sigma(p^k)$を求める関数pksumを作ります。要は整数$n$に含まれる素数すべてに関して$\sigma(p^k)$を求めてそれを$(1)$の乗法定理を使って掛ければ$\sigma(n)$が求まるということになります。

def pksum(n, p):   # calculate 1+p+p^2+p^3+,,,
  ret, pk = 1,1
  while n % p == 0:
    n //= p
    pk *= p
    ret += pk
  return ret

次にこのpksumを使って[0,nmax]の約数の和を求めるsumdivrangeを作ります。エラストテネスのふるいの原理で素数の倍数に対してpksumを計算して掛けて行きます。

後で使うかもしれないので、第二引数のproperフラグで整数自身を含むかを指定できるようにしました。

def sumdivrange(nmax, proper=False):
  sdiv = (nmax + 1) * [1]
  for p in range(2, len(sdiv)):
    if sdiv[p] > 1: continue # not prime
    for pi in range(p, len(sdiv), p):  # pi = p x i
      sdiv[pi] *= pksum(pi, p)         # 1+p+p^2+p^3+,,,
  if not proper: return sdiv
  for i in range(len(sdiv)): sdiv[i] -= i   # for proper divisor, subtract the number itself
  return sdiv

これで部品はそろったので、最初のプログラムの"spd = ..."の1行を以下に変更するだけです。実行時間は約24秒とまあまあ改善出来ました。

spd = sumdivrange(N, proper=True)  # sum of proper divisors

(開発環境:Google Colab)

1
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
1
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?