문제 링크: 18111번: 마인크래프트

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <iostream>
#include <algorithm>

int g_heightFreq[257] = {};
int g_numRows = 0;
int g_numCols = 0;
int g_numBlocks = 0;

int CalculateTime(int p_height)
{
    int time = 0;
    int numBlocks = g_numBlocks;

    for (int height = 0; height <= 256; ++height)
    {
        const int freq = g_heightFreq[height];
        int diff = height - p_height;

        // 제거
        if (diff > 0)
        {
            time += 2 * diff * freq;
            numBlocks += diff * freq;
        }
        // 설치
        else if (diff < 0)
        {
            time -= diff * freq;
            numBlocks += diff * freq;
        }
    }

    return (numBlocks < 0) ? -1 : time;
}

int main()
{
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(nullptr);

    std::cin >> g_numRows >> g_numCols >> g_numBlocks;
    int maxHeight = 0;
    int minHeight = 256;

    // 높이 정보 기록
    for (int i = 0; i < g_numRows * g_numCols; ++i)
    {
        int height;
        std::cin >> height;
        ++g_heightFreq[height];

        maxHeight = std::max(maxHeight, height);
        minHeight = std::min(minHeight, height);
    }

    int minTime = 500 * 500 * 256 * 2;
    int optimalHeight = 0;

    // 최소/최대 높이 범위의 땅 고르기 작업 시 걸리는 시간 계산
    for (int h = maxHeight; h >= minHeight; --h)
    {
        const int time = CalculateTime(h);

        // 블록이 부족한 경우
        if (time == -1)
        {
            continue;
        }
        // 기존 작업 시간보다 더 짧은 경우
        if (time < minTime)
        {
            minTime = time;
            optimalHeight = h;
        }
    }

    std::cout << minTime << " " << optimalHeight;

    return 0;
}

2. 분석

처음엔 parametric search 문제인줄 알았습니다. 땅 고르기 높이가 올라갈 수록, 작업 시간이 감소할 것이라고 예상했습니다. 만약 작업 시간이 정말 이런 단조 감소 함수라면, parametric search로 접근해서 해결할 수 있을 것 입니다. 하지만 땅 고르기 높이가 올라간다고 해서 작업 시간이 반드시 줄어들지 않습니다. 반례를 몇 가지 생각해볼 수도 있겠지만 문제에 이미 힌트가 있습니다. 문제 설명 중 작업 시간이 같을 시 높이가 더 높은 경우를 출력하라고 나와 있습니다. 이 문구에서 땅 고르기 높이가 올라간다고 해서 작업 시간이 반드시 줄어드는 것이 아니라는 것을 어느 정도 암시하고 있습니다. parametric search로 접근하지 못 한다는 것을 깨닫고 brute-force로 접근했습니다. brute-force도 처음에는 비효율적으로 접근했는데, CalculateTime 과정에서 맵의 모든 좌표를 순회하는 방식으로 시간을 계산했습니다. 이런 식이면 최악의 경우 맵이 500 * 500 크기일 때, CalculateTime 호출 시 250’000번의 반복이 발생합니다. 이 문제를 해결하고 개선한 최종 코드가 현재 코드입니다. CalculateTime에서 모든 좌표를 순회하는 방식이 아닌, 0~256까지 높이에 대해 각 높이에 해당하는 좌표의 개수를 활용하여 작업 시간을 계산합니다. 이때는 맵 크기에 무관하게 항상 257번의 반복만 합니다. 최종 코드의 시간 복잡도를 계산하겠습니다. 모든 높이에 해당하는 좌표의 개수를 기록할 때 O(M * N), 땅 고르기의 최소 작업 시간을 찾을 때 O((maxHeight - minHeight) * 257) 이므로, O(M * N + (maxHeight - minHeight) * 257)이 최종 시간 복잡도가 됩니다.