Solve problem 351
This commit is contained in:
+108
@@ -0,0 +1,108 @@
|
|||||||
|
from fractions import Fraction
|
||||||
|
|
||||||
|
|
||||||
|
def plot(n):
|
||||||
|
"""
|
||||||
|
First iteration with plotting so that I could understand
|
||||||
|
what's going on. Worked well to accomplish this plan.
|
||||||
|
|
||||||
|
1. Visualize till H(10) naively
|
||||||
|
2. See pattern
|
||||||
|
3. Generalize and compute solution
|
||||||
|
"""
|
||||||
|
import numpy as np
|
||||||
|
import matplotlib
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from matplotlib.ticker import MultipleLocator
|
||||||
|
|
||||||
|
matplotlib.use("QtAgg")
|
||||||
|
|
||||||
|
_, ax = plt.subplots()
|
||||||
|
ax.set_aspect("equal")
|
||||||
|
dx = np.cos(np.pi / 3)
|
||||||
|
dy = np.sin(np.pi / 3)
|
||||||
|
|
||||||
|
c = 0
|
||||||
|
seen_set = set()
|
||||||
|
seen = []
|
||||||
|
for i in range(n + 1):
|
||||||
|
x, y = 2 * i, 0
|
||||||
|
for _ in range(i):
|
||||||
|
for xs, ys in seen:
|
||||||
|
if x % xs == 0 and x * ys == y * xs:
|
||||||
|
assert Fraction(y, x) in seen_set
|
||||||
|
ax.plot(x * dx, y * dy, "x", color="green")
|
||||||
|
c += 1
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
ax.plot(x * dx, y * dy, "o", color="blue")
|
||||||
|
if x != 0 or y != 0:
|
||||||
|
seen_set.add(Fraction(y, x))
|
||||||
|
seen.append((x, y))
|
||||||
|
x -= 1
|
||||||
|
y += 1
|
||||||
|
|
||||||
|
ax.xaxis.set_major_locator(MultipleLocator(1))
|
||||||
|
ax.yaxis.set_major_locator(MultipleLocator(1))
|
||||||
|
ax.grid(True, alpha=0.2)
|
||||||
|
print(f"h({n}) = {c * 6}")
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
|
def quick(n):
|
||||||
|
"""
|
||||||
|
Improved version that uses slopes to identify
|
||||||
|
if a point is visible. Still way too slow for 10**9, though.
|
||||||
|
"""
|
||||||
|
c = 0
|
||||||
|
seen_set = set()
|
||||||
|
for i in range(n + 1):
|
||||||
|
x, y = 2 * i, 0
|
||||||
|
for _ in range(i):
|
||||||
|
if Fraction(y, x) in seen_set:
|
||||||
|
c += 1
|
||||||
|
else:
|
||||||
|
if x != 0 or y != 0:
|
||||||
|
seen_set.add(Fraction(y, x))
|
||||||
|
x -= 1
|
||||||
|
y += 1
|
||||||
|
return c * 6
|
||||||
|
|
||||||
|
|
||||||
|
def h(n: int) -> int:
|
||||||
|
"""Optimized version based on realization that the
|
||||||
|
totient, i.e., how many numbers don't share a non-one
|
||||||
|
divisor with the respective row can be used to calculate
|
||||||
|
how many points in that row are visible."""
|
||||||
|
totients = [i for i in range(n + 1)]
|
||||||
|
|
||||||
|
r = (n * (n + 1)) // 2 # All points in 1/6 of the hexagon
|
||||||
|
r -= 1 # totient of 1 is 1
|
||||||
|
for i in range(2, n + 1):
|
||||||
|
# Number had no factor so far and is therefore a prime.
|
||||||
|
if totients[i] == i:
|
||||||
|
# That phi(p) = p - 1:
|
||||||
|
totients[i] -= 1
|
||||||
|
# Update totients of other numbers of which i is a factor.
|
||||||
|
# This is the same as * (1 - 1 / i); since i is a factor
|
||||||
|
# //= i will always be accurate.
|
||||||
|
for j in range(i + i, n + 1, i):
|
||||||
|
totients[j] *= i - 1
|
||||||
|
totients[j] //= i
|
||||||
|
r -= totients[i]
|
||||||
|
# We only calculate the points for 1/6 of the grid.
|
||||||
|
return r * 6
|
||||||
|
|
||||||
|
|
||||||
|
def euler_351():
|
||||||
|
assert h(5) == 30
|
||||||
|
assert h(10) == 138
|
||||||
|
assert h(1000) == 1177848
|
||||||
|
assert quick(1000) == 1177848
|
||||||
|
return h(100000000)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
solution = euler_351()
|
||||||
|
print(f"e351.py: {solution}")
|
||||||
|
assert solution == 11762187201804552
|
||||||
Reference in New Issue
Block a user