Project Euler 143

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


∠BTC = ∠CTA = ∠ATB = 120°より、

a2 = q2 + qr + r2
b2 = p2 + pq + q2
c2 = r2 + rp + p2

が成り立ちます。個々の整数解はピタゴラス数と同じように求められます。

x = q / a
y = r / a

と置き換えて、

x2 + x y + y2 = 1

グラフを描くと、

斜めになった楕円です。(0, 1)を必ず通るので、この点から第1象限で再び楕円と交わるように直線を引きます。

y = -k x + 1

その条件は、1 / 2 < k < 1 です。楕円との交点は、

x2 + x(-k x + 1) + (-k x + 1)2 = 1
(1 - k + k2)x2 + (1 - 2k)x = 0
x = (2k - 1) / (1 - k + k2)
y = (1 - k2) / (1 - k + k2)

交点の座標が有理数k有理数 です。
k = n / m(m / 2 < n < m)として、分母を払って、

(q, r, a) = (m(2n - m), m2 - n2, m2 - m n + n2)

mnが素のとき、m + nが3の倍数だとq, r, aの全て3の倍数になります。その場合、

(m', n') = ((m + n) / 3, (2m - n) / 3)

とすれば、q, r, aがそれぞれ1/3になるので(qrはひっくり返ります)無視できます。


さて、このように必要なq, rの組を求めたら、これをグラフにします。すなわち、q, rを点と考えて、qrを辺で結びます。そして3点でループができていれば、その3点がp, q, rとなって題意を満たしていることになります。

from itertools import *
from fractions import gcd

def gen_qr(max_qr):
    for m in xrange(2, (max_qr + 1) / 2 + 1):
        g = takewhile(lambda n: n * (2 * m - n) <= max_qr,
                                            xrange(m / 2 + 1, m))
        for n in ifilter(lambda n: (m + n) % 3 != 0 and gcd(m, n) == 1, g):
            q = m * (2 * n - m)
            r = m * m - n * n
            if q < r:
                q, r = r, q
            for l in xrange(1, max_qr / (q + r) + 1):
                yield l * q, l * r

def make_graph(max_n):
    def add(q, r):
        if q in g:
            g[q].append(r)
        else:
            g[q] = [ r ]
    
    g = { }
    for q, r in sorted(list(gen_qr(max_n))):
        add(q, r)
        add(r, q)
    return g

def gen_triangles(g):
    for q, nodes in g.iteritems():
        # let q be the smallest
        greater_node = filter(lambda x: x > q, nodes)
        if len(greater_node) >= 2:
            for r, p in combinations(greater_node, 2):
                if g[r].count(p) == 1:
                    yield p + q + r

N = 120000
graph = make_graph(N)
s = set(n for n in gen_triangles(graph) if n <= N)
print sum(s)