Problem link: https://www.acmicpc.net/problem/3644
일단 문제 해설에 앞서서 중요한 사실을 미리 밝히면, 이 문제의 답 범위는 64bit integer를 아득히 초과한다.
답을 계산하기 위한 BigInteger 클래스는 미리 만들어야 함에 주의하자.
아래 설명에서는 BigInteger는 이미 구현했다고 가정하고, 코어가 되는 알고리즘에 대해서만 설명한다.
문제는 DP로 접근한다.
C_(n)
을 그려본 뒤에 C_(n+1)
을 그려보자.
이 때, 정점 n+1
에 의해서 그래프에 추가된 간선은 아래 그림의 적색과 같이 (n, n+1)
, (n+1, 1)
이 될 것이다.
이제 C_(n+1)
의 매칭 수를 헤아리는데, 아래와 같이 경우를 나누어 생각해보자.
매칭의 정의에 의해 (n, n+1)
, (n+1, 1)
을 모두 포함할 수는 없고(n+1
을 두번 포함하게 되므로), 따라서 자명하게 C_(n+1)
의 매칭 수는 아래 3가지 케이스의 합이 될 것이다.
(n, n+1)
, (n+1, 1)
모두를 포함하지 않는 매칭의 수(n, n+1)
만 포함하는 매칭의 수(n+1, 1)
만 포함하는 매칭의 수Case 1) 의 경우에는 C_(n)
에 존재하는 매칭들 중 (n, 1)
을 포함하지 않는 매칭들의 수와 같음을 알 수 있다.
아래 그림이 보이듯이, (n, 1)
을 포함하지 않았던 매칭들에 단순히 (n, 1)
을 (n, n+1)
, (n+1, 1)
로 바꿔치기 해놓으면 되기 때문이다.
이 때, (n, n+1)
, (n+1, 1)
모두 선택되지 않는 간선임에 주의하자.
Case 2) 의 경우에는 C_(n)
에 존재하는 매칭들 중 정점 n
을 포함하지 않는 매칭들의 수와 같음을 알 수 있다.
그런데, 정점 C_(n)
에서 정점 n
을 포함하지 않는 매칭들의 수는 곧 C_(n-1)
에서 (n-1, 1)
을 포함하지 않는 매칭들의 수와 같다.
아래 그림이 보이듯이, (n-1, 1)
을 포함하지 않았던 매칭들에 단순히 (n-1, 1)
을 (n, n+1)
, (n+1, 1)
, (n-1, n)
로 바꿔치기 해놓으면 되기 때문이다.
이 때, (n, n+1)
은 선택된 간선, (n+1, 1)
, (n-1, n)
은 선택되지 않는 간선임에 주의하자.
Case 3) 역시 Case 2) 와 유사하게 C_(n)
의 매칭들 중 정점 1
을 포함하지 않은 매칭의 수와 같음을 알 수 있다.
따라서, Case 2)와 유사한 논리의 진행이 가능한데 여기서 하나 잘 생각해볼 포인트가 있다.
주어지는 그래프는 굉장히 특이한 환형의 그래프이므로 정점 1
을 포함하지 않는 경우의 수나, 정점 2
를 포함하지 않는 경우의 수나..., 특정한 하나의 정점을 포함하지 않는 경우의 수는 모두 같을 수 밖에 없다.
따라서, Case 3)의 매칭 수는 Case 2)의 매칭 수와 같다.
아이디어를 DP로 옮기기 위한 CACHE의 정의는 아래와 같다.
CACHE[n][0]
: C_(n)
에서 간선 (n, 1)
을 포함하지 않는 매칭의 수CACHE[n][1]
: C_(n)
에 존재하는 매칭의 수점화식을 세우면 아래와 같다.
CACHE[n+1][0]
= CACHE[n][0] + CACHE[n-1][1]
// Case 1) + Case 3)CACHE[n+1][1]
= CACHE[n][0] + 2 * CACHE[n-1][0]
// Case 1) + Case 2) + Case 3)#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class Number
{
private:
string number_;
public:
Number(void) = default;
Number& operator=(const Number& rhs) = default;
explicit Number(const string& number) : number_(number)
{
}
const std::string& number(void) const
{
return number_;
}
Number operator+(const Number& rhs) const
{
string result;
size_t length = max(number().size(), rhs.number().size());
size_t sum;
size_t carry = 0;
for (size_t digit = 0; digit < length; ++digit)
{
char digit_a = (number().size() >= digit + 1) ? number()[number().size() - digit - 1] : '0';
char digit_b = (rhs.number().size() >= digit + 1) ? rhs.number()[rhs.number().size() - digit - 1] : '0';
sum = ((digit_a - '0') + (digit_b - '0') + carry) % 10;
carry = ((digit_a - '0') + (digit_b - '0') + carry) / 10;
result += string(1, static_cast<char>(sum + '0'));
}
if (carry)
{
result += string(1, static_cast<char>(carry + '0'));
}
reverse(result.begin(), result.end());
return Number(result);
}
};
const int kMaxN = 10000;
Number CACHE[kMaxN + 1][2];
int main(void)
{
// For Faster IO
ios_base::sync_with_stdio(false);
cout.tie(nullptr);
cin.tie(nullptr);
// Preprocessing
CACHE[3][0] = Number("3");
CACHE[3][1] = Number("4");
CACHE[4][0] = Number("5");
CACHE[4][1] = Number("7");
for (int n = 5; n <= kMaxN; ++n)
{
CACHE[n][0] = CACHE[n - 1][0] + CACHE[n - 2][0];
CACHE[n][1] = CACHE[n - 1][0] + CACHE[n - 2][0] + CACHE[n - 2][0];
}
// Solve
int N;
while (cin >> N)
{
cout << CACHE[N][1].number() << '\n';
}
return 0;
}