d(n)をn未満の真の約数の和としたとき、d(a)=b かつ d(b)=a であるようなa とbは友愛数と呼ばれます。 10000以下の全ての友愛数の和を求めなさい。
pe_21_1.cljは以前解いたときのものです。pe_21_2.cljは改めて見てみて気に 入らないところを直したものです。
対象となる数それぞれの約数の和を求めるわけですが、約数そのものを求めな くても約数の和を求めることができます。Wikipediaとか こことかに解説があり ますが、その数を素因数分解したものを使います。
素因数分解するために作った関数が、factorsです。 小さい素数から順に割れるだけ割って、次の素数を調べます。
次に、上の方法で必要な数列を作ります。 3^4であれば、(3^0+3^1+3^2+3^3+3^4)=(1+3+9+27+81) が欲しいのですが、こ れを作るのが、multi-plusです。
素因数分解した各成分についてそれぞれmulti-plusを作って掛けると、約数の 和になります。
ここまでできたら回答です。
範囲の数について、約数の和を求め、さらにその数の約数の和を求めて、元の数 と同じになれば友愛数です。
Elapsed time: 923.9876 msecs" 上々ですね。
自分で思いついたのだか、人から聞いたのだかわすれましたが、ある範囲の数 について、その約数の和をすべて求めるには、こんな方法もあります。
以下は、1〜8までの数の約数を並べたものです。
1 : 1 2 : 1 2 3 : 1 3 4 : 1 2 4 5 : 1 5 6 : 1 2 3 6 7 : 1 7 8 : 1 2 4 8
こうやって並べてしまうとそれだけで分ってしまうのですが、縦に見ていくと、 2を約数に持つものは2の倍数で、3を約数に持つものは3の倍数です。
この性質を使えば、機械的に表を埋めていくことができます。この先は、5,10,15...と5の倍数のところに5を入れていく、6の倍数のところに...と進めていけばいいということです。
そして、これは、ほとんどエラトステネスの篩の処理と同じです。
これを使って作ったのがsigma1-list関数です。
中身は普通に二重のループを回しているだけですが、約数の和がわかればいいので、上記の表と違って該当の数を加算しています。
long-array が返ります。clojure的には、シーケンスにして返すのが筋でしょうが、 この後の処理を考えてこうしてあります。
- iでチェックしたjは、iに含めなければ高速化しそうですが、どうしたらいいかわからない
- 友愛数を求めるという問題であれば、ペアにして出力すべきでしょうが、今回は合計してしまうので無視。
約数の和のリストを作るときに、iではなく、1を足すと、約数の個数が求まります。
ということで、パラメータで渡すようししてみました。 約数関数の添字と同じく、σx として、 σ0 は約数の個数、σ1は約数の和がでます。 ただし、一般的な理解と異なり、この処理ではNはNの約数に入っていません。
(defn sigmax-list [x ^long size]
(loop [i 2
arr (long-array size 1)]
(if (>= i (/ size 2))
arr
(recur (inc i)
(loop [a arr
j (* i 2)]
(if (>= j size)
a
(recur (do (aset a j (+ (aget a j) (long (Math/pow i x))))
a)
(+ j i))))))))
このアルゴリズムと実装ならもっと速いはず,と思ってみてみると,
find-amicable
関数が type hinting されていませんでした.これで <10ms になるはずです.あと,
for
マクロのところをloop
-recur
で書き直すか,areduce
を使えば,さらに速くなりそうですが,これはこれでそこまでしなくてもと思います.↓はそこまでした版の例,