The Way

프로젝트 오일러 44. Pentagon numbers 본문

PS/프로젝트 오일러

프로젝트 오일러 44. Pentagon numbers

Jeonggyun 2019. 7. 23. 09:30

중간에 뜬금없이 끼어있는 어려운 문제. 고민을 꽤 많이 했다.

물론 답을 때려맞추기는 꽤 쉽지만, 답의 정당성까지 보이기는 쉽지 않다.

 

문제는 다음과 같다.

오각수(pentagon number)는 Pn=n(3n1)/2의 형태로 주어진다.

앞에서부터 1, 5, 12, 22, 35, 51...이 된다.

 

이제 오각수 한 쌍 PiPj를 찾아야 하는데, 두 오각수의 합과 차가 모두 오각수가 되어야 한다.

이를 만족하는 오각수의 쌍 중 두 수의 차이의 최솟값을 구하는 문제이다.

 

 

보통은 이렇게 푼다. 적당히 오각수를 생성해놓고(만 개 정도) pair별로 확인하며 합과 차가 둘다 오각수인지 확인한다.

이렇게 코드를 작성하면, 적당히 빠른 시간 안에 5482660이라는 답을 얻을 수 있다.

조금 더 자세히 보면, P1020=1560090P2167=7042750의 차이는 P1929=5482660이고, 합은 P2395=8602840이다.

대충 인터넷을 살펴봤는데 대부분 다 이렇게 푼다.

 

하지만 이 5482660이라는 수가 과연 최소일까?

예를 들어 P1000000=1499999500000이고, P1000001=1500002500001로 둘의 차이는 3000001밖에 나지 않는데 (물론 이게 답은 아니지만) 이런 식으로 수백만 번째 오각수 중에 차이가 더 작은 답이 존재하지는 않을까?

 

 

문제를 제대로 풀기 위해 조금 다른 방법을 사용해보자. 두 오각수의 차이가 또다른 오각수가 되어야하니, 작은 오각수부터 차례대로

(i) 어떠한 두 오각수의 차이가 되는지 살펴보고

(ii) 그러한 두 오각수가 존재한다면 두 오각수의 합이 또다른 오각수가 되는지 살펴볼 것이다.

 

이를 위해서 먼저 간단히 함수 isPen()을 정의하고 가자.

임의의 자연수 n이 있을 때, n이 오각수인지 아닌지 판별할 수 있을까?

한 가지 방법으로, 위에 적힌 오각수를 생성하는 방정식 Pn=n(3n1)/2를 풀어,

x=(24n+1+1)/6으로 x를 구한 뒤, x가 정수인지 확인하는 방법이 있다.

그런데 나는 정수 문제에 실수를 쓰는 것을 별로 선호하지 않는다. 다른 방법으로는 그냥 binary search를 해주면 O(logn)에 확인할 수 있다.

 

이제 (i)번, 어떤 수 n이 두 오각수의 차이가 되는지 확인하는 것이 관건이다.

관찰을 통해, 오각수의 차이는 4, 7, 10, 13, 16, ..., 4 + 3k, ...가 되는 것을 확인할 수 있다. (오각수가 2차식이므로 당연하다)

어떤 두 오각수의 차이는, 다르게 말해 저 수열의 연속되는 부분 수열의 합과 같다.

(가령 P1P4의 차이는 4 + 7 + 10이 된다)

 

이제 n이 주어졌을 때, n이 Px+1Px가 될 수 있는지, Px+2Px가 될 수 있는지, Px+3Px가 될 수 있는지... 를 차례대로 확인할 것이다. 즉, 합이 n이 되는 해당 길이의 부분 수열이 존재하는지를 확인한다.

 

확인은 생각보다 간단한데, Px+1Px가 될 수 있는지는 자명하다. n - 4가 3의 배수이면 된다.

Px+2Px가 될 수 있는지는 n - 4 * 2 - 3 * (0 + 1)이 6의 배수가 되는지 확인하면 된다.

Px+3Px가 될 수 있는지는 n - 4 * 3 - 3 * (0 + 1 + 2)가 9의 배수가 되는지 확인하면 된다.

이런식으로 각 길이에 대해 오각수가 그 차이가 될 수 있는지를 확인하다, 특정 길이 이상일 때 아예 불가능하게 되면 이 때 break 해주면 된다.

총 소요 시간은 O(n)정도가 소요된다.

 

이렇게 확인해가며 Px+pPx가 가능한 것으로 나온다면, 위에서 정의한 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=18806537190P121168=22022465752의 차이 P46303=3215928562, 합 P164983=40829002942

 

P95506=13682046301P110461=18302393551의 차이 P55500=4620347250, 합 P146024=31984439852

 

P52430=4123331135P91650=12599537925의 차이 P75172=8476206790, 합 P105587=16722869060

 

P73745=8157450665P129198=25038120207의 차이 P106084=16880669542, 합 P148763=33195570872

 

P186517=52182793675P224159=75370773842의 차이 P124333=23187980167, 합 P291609=127553567517

'PS > 프로젝트 오일러' 카테고리의 다른 글

프로젝트 오일러 26. Reciprocal cycles  (0) 2019.07.20
Comments