The Way
프로젝트 오일러 44. Pentagon numbers 본문
중간에 뜬금없이 끼어있는 어려운 문제. 고민을 꽤 많이 했다.
물론 답을 때려맞추기는 꽤 쉽지만, 답의 정당성까지 보이기는 쉽지 않다.
문제는 다음과 같다.
오각수(pentagon number)는 Pn=n(3n−1)/2의 형태로 주어진다.
앞에서부터 1, 5, 12, 22, 35, 51...이 된다.
이제 오각수 한 쌍 Pi와 Pj를 찾아야 하는데, 두 오각수의 합과 차가 모두 오각수가 되어야 한다.
이를 만족하는 오각수의 쌍 중 두 수의 차이의 최솟값을 구하는 문제이다.
보통은 이렇게 푼다. 적당히 오각수를 생성해놓고(만 개 정도) pair별로 확인하며 합과 차가 둘다 오각수인지 확인한다.
이렇게 코드를 작성하면, 적당히 빠른 시간 안에 5482660이라는 답을 얻을 수 있다.
조금 더 자세히 보면, P1020=1560090과 P2167=7042750의 차이는 P1929=5482660이고, 합은 P2395=8602840이다.
대충 인터넷을 살펴봤는데 대부분 다 이렇게 푼다.
하지만 이 5482660이라는 수가 과연 최소일까?
예를 들어 P1000000=1499999500000이고, P1000001=1500002500001로 둘의 차이는 3000001밖에 나지 않는데 (물론 이게 답은 아니지만) 이런 식으로 수백만 번째 오각수 중에 차이가 더 작은 답이 존재하지는 않을까?
문제를 제대로 풀기 위해 조금 다른 방법을 사용해보자. 두 오각수의 차이가 또다른 오각수가 되어야하니, 작은 오각수부터 차례대로
(i) 어떠한 두 오각수의 차이가 되는지 살펴보고
(ii) 그러한 두 오각수가 존재한다면 두 오각수의 합이 또다른 오각수가 되는지 살펴볼 것이다.
이를 위해서 먼저 간단히 함수 isPen()을 정의하고 가자.
임의의 자연수 n이 있을 때, n이 오각수인지 아닌지 판별할 수 있을까?
한 가지 방법으로, 위에 적힌 오각수를 생성하는 방정식 Pn=n(3n−1)/2를 풀어,
x=(√24n+1+1)/6으로 x를 구한 뒤, x가 정수인지 확인하는 방법이 있다.
그런데 나는 정수 문제에 실수를 쓰는 것을 별로 선호하지 않는다. 다른 방법으로는 그냥 binary search를 해주면 O(logn)에 확인할 수 있다.
이제 (i)번, 어떤 수 n이 두 오각수의 차이가 되는지 확인하는 것이 관건이다.
관찰을 통해, 오각수의 차이는 4, 7, 10, 13, 16, ..., 4 + 3k, ...가 되는 것을 확인할 수 있다. (오각수가 2차식이므로 당연하다)
어떤 두 오각수의 차이는, 다르게 말해 저 수열의 연속되는 부분 수열의 합과 같다.
(가령 P1과 P4의 차이는 4 + 7 + 10이 된다)
이제 n이 주어졌을 때, n이 Px+1−Px가 될 수 있는지, Px+2−Px가 될 수 있는지, Px+3−Px가 될 수 있는지... 를 차례대로 확인할 것이다. 즉, 합이 n이 되는 해당 길이의 부분 수열이 존재하는지를 확인한다.
확인은 생각보다 간단한데, Px+1−Px가 될 수 있는지는 자명하다. n - 4가 3의 배수이면 된다.
Px+2−Px가 될 수 있는지는 n - 4 * 2 - 3 * (0 + 1)이 6의 배수가 되는지 확인하면 된다.
Px+3−Px가 될 수 있는지는 n - 4 * 3 - 3 * (0 + 1 + 2)가 9의 배수가 되는지 확인하면 된다.
이런식으로 각 길이에 대해 오각수가 그 차이가 될 수 있는지를 확인하다, 특정 길이 이상일 때 아예 불가능하게 되면 이 때 break 해주면 된다.
총 소요 시간은 O(√n)정도가 소요된다.
이렇게 확인해가며 Px+p−Px가 가능한 것으로 나온다면, 위에서 정의한 isPen()함수를 이용해 두 오각수의 합 또한 오각수가 되는지 확인해주면 된다.
코드는 다음과 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | #include <iostream> #include <cmath> using namespace std; bool isPen( long long t) { long long low = 0, high = ( long long ) sqrt (t) + 10; while (high - low != 1) { long long mid = (low + high) / 2; long long value = mid * (3 * mid - 1) / 2; if (value < t) low = mid; else high = mid; } if ((high * (3 * high - 1) / 2) == t) return 1; else return 0; } void canDif( long long n) { for ( int len = 1; ; ++len) { long long m = n - 4 * len; m -= 3 * len * (len - 1) / 2; if (m < 0) break ; if (m % (3 * len)) continue ; else { long long num = m / (3 * len) + 1; long long low = num * (3 * num - 1) / 2; if (isPen(2 * low + n)) { cout << n << endl; exit (0); } } } } int main() { long long i = 1; while (1) { canDif(i * (3 * i - 1) / 2); i++; } } |
총 소요시간은 차이가 Pn이 되는 수까지 확인하는 데에 O(n2logn)이 소요된다.
이러한 성질을 만족하는 오각수는 유일할까?
수가 워낙 커져서 더이상 c++로는 구현하기가 버거워진다. python을 이용해 마저 계산해보자.
오랜 시간이 지난 후에 몇 가지 해를 더 찾을 수 있었다.
P111972=18806537190과 P121168=22022465752의 차이 P46303=3215928562, 합 P164983=40829002942
P95506=13682046301과 P110461=18302393551의 차이 P55500=4620347250, 합 P146024=31984439852
P52430=4123331135과 P91650=12599537925의 차이 P75172=8476206790, 합 P105587=16722869060
P73745=8157450665과 P129198=25038120207의 차이 P106084=16880669542, 합 P148763=33195570872
P186517=52182793675과 P224159=75370773842의 차이 P124333=23187980167, 합 P291609=127553567517
'PS > 프로젝트 오일러' 카테고리의 다른 글
프로젝트 오일러 26. Reciprocal cycles (0) | 2019.07.20 |
---|