748 Upside down Diophantine equation.sf 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. #!/usr/bin/ruby
  2. # Daniel "Trizen" Șuteu
  3. # Date: 27 February 2021
  4. # https://github.com/trizen
  5. # Upside down Diophantine equation
  6. # https://projecteuler.net/problem=748
  7. # The problem asks for integer solutions to:
  8. # 1/x^2 + 1/y^2 = 13/z^2
  9. # It is easy to see that:
  10. # (x^2 + y^2)/13 = v^4, for some integer v.
  11. # Multiplying both sides by 13, we have:
  12. # x^2 + y^2 = v^4 * 13
  13. # By finding integer solutions (x,y) to the above equation, we can then compute `z` as:
  14. # z = sqrt((x^2 * y^2 * 13)/(x^2 + y^2))
  15. # z = sqrt((x^2 * y^2) / v^4)
  16. # We need to iterate over 1 <= v <= sqrt(N/3).
  17. func sum_of_two_squares_solutions(n) is cached {
  18. n == 0 && return [[0, 0]]
  19. var prod1 = 1
  20. var prod2 = 1
  21. var prime_powers = []
  22. for p,e in (n.factor_exp) {
  23. if (p % 4 == 3) { # p = 3 (mod 4)
  24. e.is_even || return [] # power must be even
  25. prod2 *= p**(e >> 1)
  26. }
  27. elsif (p == 2) { # p = 2
  28. if (e.is_even) { # power is even
  29. prod2 *= p**(e >> 1)
  30. }
  31. else { # power is odd
  32. prod1 *= p
  33. prod2 *= p**((e - 1) >> 1)
  34. prime_powers.append([p, 1])
  35. }
  36. }
  37. else { # p = 1 (mod 4)
  38. prod1 *= p**e
  39. prime_powers.append([p, e])
  40. }
  41. }
  42. prod1 == 1 && return [[prod2, 0]]
  43. prod1 == 2 && return [[prod2, prod2]]
  44. # All the solutions to the congruence: x^2 = -1 (mod prod1)
  45. var square_roots = gather {
  46. gather {
  47. for p,e in (prime_powers) {
  48. var pp = p**e
  49. var r = sqrtmod(-1, pp)
  50. take([[r, pp], [pp - r, pp]])
  51. }
  52. }.cartesian { |*a|
  53. take(Math.chinese(a...))
  54. }
  55. }
  56. var solutions = []
  57. for r in (square_roots) {
  58. var s = r
  59. var q = prod1
  60. while (s*s > prod1) {
  61. (s, q) = (q % s, s)
  62. }
  63. solutions.append([prod2 * s, prod2 * (q % s)])
  64. }
  65. for p,e in (prime_powers) {
  66. for (var i = e%2; i < e; i += 2) {
  67. var sq = p**((e - i) >> 1)
  68. var pp = p**(e - i)
  69. solutions += (
  70. __FUNC__(prod1 / pp).map { |pair|
  71. pair.map {|r| sq * prod2 * r }
  72. }
  73. )
  74. }
  75. }
  76. solutions.map {|pair| pair.sort } \
  77. .uniq_by {|pair| pair[0] } \
  78. .sort_by {|pair| pair[0] }
  79. }
  80. func S(N) {
  81. var total = 0
  82. for v in (1 .. floor(sqrt(N/3))) {
  83. var solutions = sum_of_two_squares_solutions(13 * v**4) || next
  84. for x,y in (solutions) {
  85. y <= N || next
  86. var z = (x*x * y*y)/(v**4)
  87. if (z.is_square) {
  88. z = z.isqrt
  89. z <= N || next
  90. if (gcd(x,y,z) == 1) {
  91. say [x,y,z,v]
  92. total += [x,y,z].sum
  93. assert_eq(1/x**2 + 1/y**2, 13/z**2)
  94. }
  95. }
  96. }
  97. }
  98. return total
  99. }
  100. assert_eq(S(1e2), 124)
  101. assert_eq(S(1e3), 1470)
  102. assert_eq(S(1e5), 2340084)
  103. var total = S(1e16)
  104. say "Total: #{total} -> #{total % 1e9}"