128 lines
3.5 KiB
Python
128 lines
3.5 KiB
Python
import math
|
|
import concurrent.futures
|
|
from collections import defaultdict
|
|
|
|
|
|
def attempt_1(a, b, c):
|
|
""" For the triangle with the sites a, b, c, calculate
|
|
the coordinates of C where we define A to be at (0, 0),
|
|
and B at (c, 0). """
|
|
assert a + b > c
|
|
assert a + c > b
|
|
assert b + c > a
|
|
S2 = [s**2 for s in range(120000)]
|
|
S4 = [s**4 for s in range(120000)]
|
|
|
|
a2 = S2[a]
|
|
b2 = S2[b]
|
|
c2 = S2[c]
|
|
|
|
a4 = S4[a]
|
|
b4 = S4[b]
|
|
c4 = S4[c]
|
|
|
|
# I've derived this myself but unfortunately I was completely on the wrong
|
|
# track.
|
|
x = -3 * (a4 + b4 + c4) + 6 * (a2*b2 + a2*c2 + b2*c2)
|
|
y = math.isqrt(x)
|
|
# y = math.sqrt(-3 * (a4 + b4 + c4) + 6 * (a2*b2 + a2*c2 + b2*c2))
|
|
if not y * y == x:
|
|
return "no int"
|
|
|
|
d = math.isqrt((c2 + b2 + a2 + y) // 2)
|
|
if d * d * 2 == c2 + b2 + a2 + y:
|
|
return d
|
|
else:
|
|
return "no int"
|
|
|
|
|
|
def is_square(n):
|
|
i = math.isqrt(n)
|
|
return i * i == n
|
|
|
|
|
|
def check_range(start, end, limit):
|
|
# When you realize that the angles in the middle are all 120 degress, you
|
|
# can derive the following formula with the law of cosines:
|
|
# c**2 = p**2 + p * r + r**2
|
|
rs = set()
|
|
for q in range(start, end):
|
|
for r in range(1, q):
|
|
if q + r > limit:
|
|
break
|
|
a2 = q**2 + q * r + r**2
|
|
if not is_square(a2):
|
|
continue
|
|
for p in range(1, r):
|
|
if q + r + p > limit:
|
|
break
|
|
b2 = p**2 + p * q + q**2
|
|
if not is_square(b2):
|
|
continue
|
|
|
|
c2 = p**2 + p * r + r**2
|
|
if is_square(c2):
|
|
d = p + q + r
|
|
# assert 3*(a**4 + b**4 + c**4 + d**4) == (a**2 + b**2 + c**2 + d**2)**2
|
|
print(math.sqrt(a2), math.sqrt(b2), math.sqrt(c2), d)
|
|
if d < limit:
|
|
rs.add(d)
|
|
return rs
|
|
|
|
|
|
def euler_143_brute_foce():
|
|
m = 120_000
|
|
range_size = 1000
|
|
s = set()
|
|
with concurrent.futures.ProcessPoolExecutor() as executor:
|
|
futures = []
|
|
for start in range(1, m, range_size):
|
|
end = min(start + range_size, m)
|
|
futures.append(executor.submit(check_range, start, end, m))
|
|
for future in concurrent.futures.as_completed(futures):
|
|
s |= future.result()
|
|
return sum(s)
|
|
|
|
|
|
def euler_143():
|
|
pairs = defaultdict(set)
|
|
limit = 120_000
|
|
|
|
# Instead of bruteforcing, we can calculate 120 degree triangles directly.
|
|
#
|
|
# https://en.wikipedia.org/wiki/Integer_triangle#Integer_triangles_with_a_120%C2%B0_angle
|
|
#
|
|
# We then map one 120 angle adjacent site to another one.
|
|
for m in range(1, math.isqrt(limit)):
|
|
for n in range(1, m):
|
|
a = m**2 + m * n + n**2
|
|
b = 2*m*n + n**2
|
|
c = m**2 - n**2
|
|
assert a**2 == b**2 + b * c + c**2
|
|
|
|
k = 1
|
|
while k * (b + c) < limit:
|
|
pairs[k * b].add(k * c)
|
|
pairs[k * c].add(k * b)
|
|
k += 1
|
|
|
|
# Which ultimately allows us to construct all the triangles by iterating
|
|
# over the sides and looking up the respective next side.
|
|
xs = set()
|
|
for q in pairs:
|
|
for r in pairs[q]:
|
|
for p in pairs[r]:
|
|
if q in pairs[p]:
|
|
s = q + r + p
|
|
if s < limit:
|
|
xs.add(s)
|
|
|
|
return sum(xs)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
solution = euler_143()
|
|
print("e143.py: " + str(solution))
|
|
assert solution == 30758397
|
|
|