ScalaでProject Euler(51)

Problem 29

もう一度考え直してみましょう。N = 100で底8の重複を考えてみましょう。底2に重複するのは公差1で33まで、底4は公差2で66まで。とりあえず81も含めて両方とも33個あります。ただし、ダブっている部分があって、公差2で33までだから16個あるので33 + 33 - 16 = 50で、81の分を引いて49個です。包除原理ですね。
包除原理は、次のように考えるとわかりやすいかもしれません。全体集合Uと部分集合S1, S2, ... , Snとして、

U - S1∪S2∪...∪Sn = ( U - S1 )×( U - S2 )×...×( U - Sn )

ここで、×は

|A|×|B| = |A∩B|

です。明らかに記号として間違っていますが、あくまでイメージです。
ここで、S1を先ほどの底2に重複する集合、S2を底4に重複する集合として、

S1 = (1, 33, 1)
S2 = (2, 66, 1)

と書きます。(公差, 上限, 交わる集合の個数)です。そうすると、

S1×S2 = (2, 33, 2)

となります。一般には、

(d1, u1, n1)×(d2, u2, n2) = (lcm(d1, d2), min(u1, u2), n1 + n2)

という簡単な計算になります。符号は交わる集合の個数をたよりにして底8を計算すると、

100 - 33 / 1 - 66 / 2 + 33 / 2 - 1 = 49

となります。

min

小さいほうを選ぶには、

min(1, 2)

ではなく、

1.min(2)

なんですね。オブジェクト指向は対称性の犠牲の上に成り立っています。

さて、以上の考えのもとに下のように組んでみました。たぶん、(d, u, n)で、d > uとなったら無視するというのが高速化のポイントです。これで、メモリの制限で7×108が限界でした。4秒弱です。Iteratorを使っているのになぜメモリを食うのでしょうね。


(追記)
少しコードを間違えていて、修正したコードで5×108までで1.3秒程度。6×108にすると30秒くらいかかっていました。原因不明です。


(追記)
単に素数乗のとき遅くて、29乗のときの計算をしなければならないと遅い、ということのようです。

import scala.math
import scala.testing.Benchmark

def gcd(n :Int, m :Int) :Int =
    if(m == 0) n else gcd(m, n % m)

def lcm(n :Int, m :Int) = n / gcd(n, m) * m

def pow(n :Int, e :Int) :Int =
    if(e == 0)
        1
    else {
        val m = pow(n, e / 2)
        if(e % 2 == 1)
            m * m * n
        else
            m * m
    }

// [n^(1/e)]
def int_root(n :Int, e :Int) = {
    def dec(m :Int) :Int =
        if(pow(m - 1, e) < n) m else dec(m - 1)
    
    def inc(m :Int) :Int =
        if(pow(m + 1, e) > n) m else inc(m + 1)
    
    val m = math.pow(n, 1. / e).toInt
    if(pow(m, e) > n) dec(m) else inc(m)
}

def divs(n :Int) =
    Iterator.range(1, n).filter(n % _ == 0)

def num_pows() = {
    val a = Iterator.from(2).map(int_root(N, _) - 1).takeWhile(0 <).toArray
    for(e <- Iterator.range(a.size + 1, 3, -1); e2 <- divs(e).drop(1))
        a(e2 - 2) -= a(e - 2)
    Iterator.range(0, a.size).map(a(_))
}

def fraction(num :Int, den :Int) = {
    val d = gcd(num, den)
    (num / d, den / d)
}

def count_overlap_old(e :Int) = {
    val a = new Array[Boolean](N + 1)
    val fs = List.range(1, e).map(fraction(_, e)).toMap.toList
    for((n, d) <- fs; k <- Iterator.range(n, N / d * n + 1, n))
        a(k) = true
    Iterator.range(2, N + 1).count(k => a(k))
}

def count_overlap(e :Int) :Int = {
    def mul(x :(Int,Int,Int), y :(Int,Int,Int)) = {
        val ((d1, u1, n1), (d2, u2, n2)) = (x, y)
        (lcm(d1, d2), u1.min(u2), n1 + n2)
    }
    
    def mul_iter(it_x :Iterator[(Int,Int,Int)], y :(Int,Int)) =
        it_x.flatMap(x => Iterator((1, N, 0), (y._1, N / y._2 * y._1, 1)).
                                map(mul(x, _)).filter(x => x._1 <= x._2))
    
    val fs = List.range(1, e).map(fraction(_, e)).toMap.toIterator
    val it_n = fs.foldLeft(Iterator((1, N, 0)))(mul_iter)
    N - it_n.map(x => if(x._3 % 2 == 0) x._2 / x._1 else -x._2 / x._1).sum - 1
}

val N = 5e8.toInt
def solve() = {
    val a = num_pows()
    val it = a.zip(Iterator.from(2).map(count_overlap))
    println ((N.toLong - 1) * (N - 1) - it.map(x => x._1.toLong * x._2).sum)
}

object Test extends Benchmark {
    def run() = solve
}

println (Test.runBenchmark(10))