정수 둘레와 정수 넓이를 갖는 거의 정삼각형 찾기
다음 그림과 같이 빗변을 , 밑변을 라 하면, 인 경우가 문제에서 말하는 거의 정삼각형이라 할 수 있다. 삼각형의 높이를 라 하면 피타고라스 정리에 의해 다음 관계가 성립한다.
여기에 을 대입해 정리하면 다음 식을 얻을 수 있다.
양변에 3을 곱한 다음 의 제곱 형태로 수식을 변형할 수 있다.
다시 양변을 4로 나누어 정리하면 다음 식을 얻을 수 있다.
여기서 로, 로 치환하면 다음 식을 얻는다. 문제 66에서 다루었던 펠 방정식이 된다.
펠 방정식 의 해를 구하는 방법은 위키피디아에 나와있다.
여기서 은 3이고, 첫 해 이다. 따라서 가 주어졌을 때 을 구하는 함수는 다음과 같이 작성할 수 있다.
(defn next-pair [[x y]]
[(+' (*' 2 x) (*' 3 1 y)) ;next-x
(+' (*' 2 y) (*' 1 x))]) ;next-y
주어진 에 대해 삼각형의 둘레길이와 넓이를 구하는 함수는 다음과 같이 작성할 수 있다. 인 경우와 인 경우를 쌍으로 구한다.
(defn perimeter [x]
[(+ (* 2 x) 2) ;for case 1 (a, a, a+1)
(- (* 2 x) 2)]) ;for case 2 (a, a, a-1)
(defn area [[x y]]
[(* (/ (+ x 2) 3) y) ;for case 1 (a, a, a+1)
(* (/ (- x 2) 3) y)]) ;for case 2 (a, a, a-1)
변의 길이는 로 나타내야 하므로 를 로 변환하는 함수가 필요하다. 이므로 를 알면 는 쉽게 구할 수 있다.
(defn x-to-a [x]
[(/ (+ (* 2 x) 1) 3) ;for case 1 (a, a, a+1)
(/ (- (* 2 x) 1) 3)]) ;for case 2 (a, a, a-1)
가 주어지면 변의 길이, 넓이, 둘레길이를 한꺼번에 구하는 함수는 다음과 같이 작성할 수 있다.
(defn side-area-perimeter [[x y]]
(let [[s1 s2] (x-to-a x)
[a1 a2] (area [x y])
[p1 p2] (perimeter x)]
[{:side s1 :area a1, :perimeter p1}
{:side s2 :area a2, :perimeter p2}]))
이제 문제를 다음과 같이 풀 수 있다. 변의 길이, 넓이, 둘레길이를 구해 둘레길이가 10억보다 작거나 같으면서 변의 길이가 정수인 경우, 넓이가 정수인 경우만 걸러낸 다음 둘레길이를 모두 더하면 된다.
(def limit 1000000000)
(defn solve []
(->> (iterate next-pair [2 1])
(mapcat side-area-perimeter)
(filter (fn [{s :side}] (integer? s)))
(filter (fn [{a :area}] (and (< 0 a) (integer? a))))
(take-while (fn [{p :perimeter}] (<= p limit)))
(map (fn [{p :perimeter}] p))
(apply +)))
실행 결과는 다음과 같다. 빠르게 답을 구한다.
p094=> (time (solve)) "Elapsed time: 0.92142 msecs" 5184??346