Problem #66 says:
Consider quadratic Diophantine equations of the form:
x^(2) – Dy^(2) = 1
For example, when D=13, the minimal solution in x is 649^(2) – 13×180^(2) = 1.
It can be assumed that there are no solutions in positive integers when D is square.
By finding minimal solutions in x for D = {2, 3, 5, 6, 7}, we obtain the following:
3^(2) – 2×2^(2) = 1
2^(2) – 3×1^(2) = 1
9^(2) – 5×4^(2) = 1
5^(2) – 6×2^(2) = 1
8^(2) – 7×3^(2) = 1Hence, by considering minimal solutions in x for D ≤ 7, the largest x is obtained when D=5.
Find the value of D ≤ 1000 in minimal solutions of x for which the largest value of x is obtained.
So there are several ways to tackle this problem at first glance; Just role a pair of nested loops and try all the possible combinations until you get a working one then keeping track of the max value for X and D, the second is to find something else because the problems after number 10 are not conducive to a brute force approach. The first option isn’t feasible because the number of X,Y pairs you have to check to find a solution is exponentially greater than it appears at first glance. So we need to find something else.
What we can find with a simple Wikipedia search is that this particular form of equation is called a “Pell Equation” (Oddly enough it seems to be misnamed — look it up). This gives us some information that was already in the problem statement and a method for attacking the problem more efficiently. We’ve read in two different sources now that we don’t want D to be a perfect square; so the first thing we need is a simple test to check if a number is a perfect square.
//This is separated out because it's useful in other problems too.
let PerfectSquare (d:int) =
let sqrtd = Math.Sqrt((double d))
if ((double d) % sqrtd = 0.0) then
true
else
false
Just grab the square root of a number and then take the number mod it’s square root. if you end up with no remainder you have a perfect square and we want to skip that number. There might be faster methods but for this solution and the amount of time we call it this works well enough. So what next? Well the solution I went with is the continued fraction method; so we’ll need a method to generate continued fractions. Specifically we want to generate a Continued fraction set going towards the square root of the D value we’re currently checking. Then those values are used to calculate the fundamental solution to the Pell Equation which gives us the minimal X value for a given D value.
Full solution provided in F# and was developed with the F# mono-develop plug-in. Runs in under a second and prints the D value and the maximum X value; the X value should explain why using a nested loop is a really bad idea.
open System
open System.Numerics
open System.Diagnostics
open System.Collections.Generic
let GenerateCFraction (target:int64) =
let e = new System.Collections.Generic.List<int64>()
let SqrtTarget = int64 (Math.Sqrt(double target))
let a = ref SqrtTarget
let p = ref 0L
let q = ref 1L
e.Add(!a)
p := !a * !q - !p
q := (target - !p * !p) / !q
a := (!p + SqrtTarget) / !q
while !q <> 1L do
e.Add(!a)
p := !a * !q - !p
q := (target - !p * !p) / !q
a := (!p + SqrtTarget) / !q
e.Add(!a)
e
let SolvePell (cFrac:System.Collections.Generic.List<int64>) =
let l = cFrac.Count - 1
let per =
if l % 2 = 0 then
l - 1
else
2 * l - 1
let a = ref (BigInteger cFrac.[0])
let a1 = ref 1I
let b = ref 1I
let b1 = ref 0I
let t = ref 0I
a := (BigInteger cFrac.[0])
b := ((BigInteger cFrac.[0]) * !b + !b1)
for i = 1 to (per) do
t := !a
a := (BigInteger cFrac.[(i-1)% l + 1]) * !a + !a1
a1 := !t
t := !b
b := (BigInteger cFrac.[(i-1)% l + 1]) * !b + !b1
!a
let PerfectSquare (d:int) =
let sqrtd = Math.Sqrt((double d))
if ((double d) % sqrtd = 0.0) then
true
else
false
[<EntryPoint>]
let main args =
let sw = new Stopwatch()
sw.Start()
let x = ref 0I
let maxx = ref 0I
let maxd = ref 0
for d = 2 to 1001 do
if (PerfectSquare d) then
ignore
else
let c = GenerateCFraction (int64 d)
x := (SolvePell c)
if (!x > !maxx) then
maxx := !x
maxd := d
ignore
sw.Stop()
Console.WriteLine sw.Elapsed
Console.WriteLine !maxd
Console.WriteLine !maxx
0