Project Euler Problem 23
問題23: 2つの過剰数の和で書き表せない正の整数の総和を求めよ.(28123より大きい整数は2つの過剰数の和で表せることがわかっている)。[=>問題文]
だんだん手ごたえのある問題になってきましたね。
問題23の解答
;; Emacs Lisp (require 'math-lib) ; primes-sieve, get-divisors (require 'cl) (defun problem023 (n) (let* ((abundant-numbers (make-vector n 0)) (last-aset-index 1) (primes (primes-sieve (1+ (truncate (sqrt n))))) ;; 真の約数の和を計算する関数 (sum-of-divisors (lambda (n) (- (apply #'+ (get-divisors n primes)) n))) ;; 2つの過剰数の和で表せるかチェックする関数 (sum-of-abundants-number-p (lambda (n) (let* ((result nil) (i 2) (j last-aset-index) (ni (aref abundant-numbers i)) (nj (aref abundant-numbers j))) (while (and (null result) (<= i j)) (let ((sum (+ ni nj))) (cond ((< sum n) (setq ni (aref abundant-numbers (incf i)))) ((> sum n) (setq nj (aref abundant-numbers (decf j)))) (t (setq result t))))) result)))) ;; メインループ (do ((k 1 (1+ k)) (ans 0)) ((> k n) ans) (unless (funcall sum-of-abundants-number-p k) (incf ans k)) ;; 過剰数ならば vector へ追加 (if (< k (funcall sum-of-divisors k)) (aset abundant-numbers (incf last-aset-index) k))))) (problem023 28123)
配列 abundant-numbers に順に過剰数を保存してゆきます。これを使って「2つの過剰数の和」で表せるかどうかの判定します。判定関数 sum-of-abundants-number-p では、インデックス i を abundant-numbers の先頭からインクリメント、j を(nより小さい過剰数の)末尾からデクリメントしてゆき、それらが指し示す2数の和を n に近づけていきます。(> i j) となったら該当する過剰数のペアは存在しません。なお判定処理のインデックス移動の都合から、abundant-numbers の最初の2要素は 0 固定で、インデックス2から過剰数を埋めています。
今回 Core2 Quad 6600 と Pentium4(PrescottNorthwood)コアのCeleronで試してみました。結果は順に 3.8秒と 75.8秒。ハードウェアの差が大きく反映されました。 うそでした、多分Celeronマシンは byte-comile してなかったのかな。改めて計測しなおしました。
Function Avg(sec) CPU ========== ======== ======================= problem023 3.766000 Core2 Quad 6600 2.4GHz problem023 9.170000 Celeron (Pentium4 Northwood コア) 2.4GHz
それでも同じクロック数であるにもかかわらず 約 2.4 倍の差が出ました。キャッシュ差かマルチコアの能力か。