Project Euler 106(1)

http://projecteuler.net/index.php?section=problems&id=106


まず、可能なBとCの組合せの数を数えておきましょう。まずBを一つ以上選びます。そして、残った中から一つ以上選びます。

nC1(n-1C1 + … n-1Cn-1) + … + nCn-11C1
= nC1(2n-1 - 1) + … + nCn-1(21 - 1)
= nC12n-1 + … + nCn-121 - (nC1 + … + nCn-1)
= 3n - (2n + 1) - (2n - 2)
= 3n - 2n+1 + 1

実際にはBとCが入れ替わっただけのペアを重複して数えているので、この半分になります。

(3n + 1) / 2 - 2n

さて、例えばA = { a1, a2, a3, a4 } として、

a1 < a2 < a3 < a4

とすると、

B = { a1, a4 }
C = { a2, a3 }

なら、S(B) = S(C)となるかどうかわからないのですが、

B = { a1, a3 }
C = { a2, a4 }

なら、S(B) < S(C)となるので、調べる必要がありません。
BとCを並べて、(Bが必ずa1を含むとすると)先頭からインデックスを見て、常にBよりCのほうが小さければ調べる必要がないと判定されます。一つでもそれに反すれば調べる必要があることになります。

また、n = 6のでBとCをあわせて要素数4の場合、6つの中からどの4つを選んでも調べる必要があるBとCの組合せの数は同じです。だから、一つの組合せについて調べて6C4倍すればよいです。

from itertools import *
import time

def C(n, m):
    return 1 if m == 0 else C(n, m - 1) * (n - m + 1) / m

def gen_pair_of_sets(n):
    def gen(a, p, q):
        if p == 0:
            yield (), a
        elif q == 0:
            yield a, ()
        else:
            for a1, a2 in gen(a[1:], p - 1, q):
                yield (a[0],) + a1, a2
            for a1, a2 in gen(a[1:], p, q - 1):
                yield a1, (a[0],) + a2
    
    for a1, a2 in gen(tuple(range(1, n)), n / 2 - 1, n / 2):
        yield (0,) + a1, a2

def needs_rule1(a1, a2):
    return not all(a1[k] < a2[k] for k in xrange(len(a1)))

def count_pair_of_sets(n):
    counter = 0
    for m in range(4, n + 1, 2):
        counter += C(n, m) * sum(starmap(needs_rule1,
                                        gen_pair_of_sets(m)))
    return counter

t = time.clock()
N = 20
print count_pair_of_sets(N)
print time.clock() - t