포스트

[백준][12852] 1로 만들기 2

문제

12852번


제약 확인

시간 제한이 0.5초 이므로 50’000’000 번 이내 연산으로 결과를 출력해야 합니다.
입력 크기가 \(10^6\) 이므로 만약 \(O(N^2)\) 인 경우 100’000’000’000 가 되므로
시간 복잡도가 \(O(N)\) 가 보장되는 알고리즘이 필요합니다.

알고리즘 선택하기

최소 횟수 라는 키워드로 최단 경로 알고리즘 혹은 다이나믹 프로그래밍을 고려할 수 있지만
다이나믹 프로그래밍의 핵심 개념인 Top-down, Bottom-up 에 문제가 적합하기 때문에
다이나믹 프로그래밍으로 풀어보겠습니다.

문제 해결하기

다이나믹 프로그래밍을 풀기 위해서는 작은 문제와 큰 문제 사이 점화식 을 구해야 합니다.
먼저, 10부터 2까지 차례대로 최소 횟수를 모두 직접 구해보겠습니다.

12852번


규칙을 찾아보면 N 에서 최소 횟수는 (직전 최소 연산 횟수) + 1 이라는 것을 확인할 수 있습니다.
따라서 다이나믹 프로그래밍 기법 중 Bottom-up 방식이 적합합니다.

N 번째 최소 연산 횟수는 3가지 연산 방법 중 가장 작은 값에 1을 더한 값이므로 점화식은 다음과 같습니다.

\(DP[N] = 1 + min(DP[N / 3], DP[N / 2], DP[N - 1])\)

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
#include <iostream>
#include <vector>

using namespace std;

int main() {
    int N;
    cout.tie(0)->sync_with_stdio(false);
    cin >> N;

    vector<vector<int>> result(N + 1, vector<int>());
    result[1].emplace_back(1);

    for(int curr = 2; curr <= N; ++curr) {
        int MIN = result[curr - 1].size();

        result[curr] = result[curr - 1];

        if((curr % 3 == 0) && (result[curr / 3].size() < MIN)) {
            MIN = result[curr / 3].size();
            result[curr] = result[curr / 3];
        }
        if((curr % 2 == 0) && (result[curr / 2].size() < MIN)) {
            MIN = result[curr / 2].size();
            result[curr] = result[curr / 2];
        }

        result[curr].emplace_back(curr);
    }

    cout << result[N].size() - 1 << "\n";
    for(int i = result[N].size() - 1; i >= 0; --i) {
        cout << result[N][i] << " ";
    }

    return 0;
}


반복문 안에서 작은 문제를 큰 문제로 합치는 방법은
먼저, 직전 최소 횟수(result[curr - 1].size()) 를 현재 최솟값(MIN)으로 저장합니다.
현재 N 값(curr)까지의 최소 횟수 값을 curr - 1 위치의 최솟값으로 저장합니다.
그리고 2, 3으로 나누어 떨어지면서 MIN 보다 작은 값이 존재할 때,
현재 값(result[curr])을 갱신합니다.
마지막으로 현재 값을 만들기 위한 숫자들을 배열에 넣으면 정답을 구할 수 있습니다.

참고

[BOJ_12852] 1로 만들기 2
D.JOUNG 님 블로그

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.