티스토리 뷰

Solved.ac Class 완전정복 프로젝트

Class : 2 ~ 2 ++

 


 

링크

https://www.acmicpc.net/problem/18111

 

18111번: 마인크래프트

팀 레드시프트는 대회 준비를 하다가 지루해져서 샌드박스 게임인 ‘마인크래프트’를 켰다. 마인크래프트는 1 × 1 × 1(세로, 가로, 높이) 크기의 블록들로 이루어진 3차원 세계에서 자유롭게

www.acmicpc.net

문제

팀 레드시프트는 대회 준비를 하다가 지루해져서 샌드박스 게임인마인크래프트를 켰다. 마인크래프트는 1 × 1 × 1(세로, 가로, 높이) 크기의 블록들로 이루어진 3차원 세계에서 자유롭게 땅을 파거나 집을 지을 수 있는 게임이다.

목재를 충분히 모은 lvalue는 집을 짓기로 하였다. 하지만 고르지 않은 땅에는 집을 지을 수 없기 때문에 땅의 높이를 모두 동일하게 만드는땅 고르기작업을 해야 한다.

lvalue는 세로 N, 가로 M 크기의 집터를 골랐다. 집터 맨 왼쪽 위의 좌표는 (0, 0)이다. 우리의 목적은 이 집터 내의 땅의 높이를 일정하게 바꾸는 것이다. 우리는 다음과 같은 두 종류의 작업을 할 수 있다.

좌표 (i, j)의 가장 위에 있는 블록을 제거하여 인벤토리에 넣는다.
인벤토리에서 블록 하나를 꺼내어 좌표 (i, j)의 가장 위에 있는 블록 위에 놓는다.
1번 작업은 2초가 걸리며, 2번 작업은 1초가 걸린다. 밤에는 무서운 몬스터들이 나오기 때문에 최대한 빨리 땅 고르기 작업을 마쳐야 한다. ‘땅 고르기작업에 걸리는 최소 시간과 그 경우 땅의 높이를 출력하시오.

, 집터 아래에 동굴 등 빈 공간은 존재하지 않으며, 집터 바깥에서 블록을 가져올 수 없다. 또한, 작업을 시작할 때 인벤토리에는 B개의 블록이 들어 있다. 땅의 높이는 256블록을 초과할 수 없으며, 음수가 될 수 없다.

입력
첫째 줄에 N, M, B가 주어진다. (1 ≤ M, N ≤ 500, 0 ≤ B ≤ 6.4 × 107)

둘째 줄부터 N개의 줄에 각각 M개의 정수로 땅의 높이가 주어진다. (i + 2) 번째 줄의 (j + 1) 번째 수는 좌표 (i, j)에서의 땅의 높이를 나타낸다. 땅의 높이는 256보다 작거나 같은 자연수 또는 0이다.

출력
첫째 줄에 땅을 고르는 데 걸리는 시간과 땅의 높이를 출력하시오. 답이 여러 개 있다면 그중에서 땅의 높이가 가장 높은 것을 출력하시오.

접근방법

 땅을 평평하게 만들 수 있는 최소 시간과 그때의 높이를 출력하는 문제이다. 블록을 제거하는 것은 2초가 걸리며, 블록을 놓는 것은 1 초가 걸린다. 또한, 가지고 있는 블록의 수가 제한되어있기 때문에 블록을 무조건 쌓아 놓는 것도 불가능하다. 정리하면, 가지고 있는 블록에 한하여 최대한 쌓지만, 없다면 나머지 공간은 제거하여 평평하게 만들어야 한다. 정도가 되겠다. 이 정도 생각하고 풀이로 가보자. 

실패 원인

 나는 마인크래프트 영상을 꽤 본 적이 있지만, 해보지 않았다. 그래서 인벤토리에 있는 블록만 사용할 수 있는 줄 알았다. 제일 낮은 곳부터 인벤토리에 남은 블록을 쌓고, 나머지는 밀어서 답을 도출했다. 그러나 계속 틀리는 것이다. 블로그들의 해설을 찾아보니, 무언가 이상했다. 그러니 틀릴 수밖에 없었다. 문제에서는 써주지 않았지만, 블록을 제거하면, 해당 블록이 아이템처럼 인벤토리에 추가되는 형식이다.   내 실패 원인은 게임을 안 해본 탓이다.  

풀이

 블록을 제거할 때마다 인벤토리에 블록의 개수가 추가되기 때문에 인벤토리 블록을 기준으로 최소 시간을 구하려고 하면, 생각해야 할 상황이 많다. 경우의 수를 줄이기 위해, 모든 가능성을 포함할 수 있는 범주를 제한해보자. 평평한 땅을 가장 빠르게 만들 때, 최악의 경우라도 제일 낮은 블록 이하로 제거하거나, 최대 블록 이상으로 쌓는 것은 빠를 수가 없다. 따라서 평평하게 만들 높이의 기준을  최소 블록과 최대 블록 기준으로 범위를 정한다면, 모든 경우의 수를 포함하는 범주를 만들 수 있다. 

"그래도 인벤토리 블록 개수를 고려해야 하는 건 마찬가지 아닌가?"

 나 역시 이 부분 때문에 고민이 많이 되었다. 하지만 이 부분 때문에 로직이 유연하게 나오지 않는다면, 반대로 가능한 모든 경우의 수에서 인벤토리 블록 개수에 맞지 않는 부분을 빼면 된다는 해법을 찾았다. 

최소 블록과 최대 블록을 기준으로 하여 인벤토리 안의 블록 개수와 상관없이 시간을 구하고, 인벤토리 안의 블록보다 많이 사용하였다면, 해당 시간은 사용하지 않게 제어해준다면, 문제가 없을 것이다. 

코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {

    public static void main(String[] args) throws IOException {
        Main test = new Main();

    }

    public Main() throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer stk = new StringTokenizer(br.readLine(), " ");
        int row = Integer.parseInt(stk.nextToken());
        int col = Integer.parseInt(stk.nextToken());
        int B = Integer.parseInt(stk.nextToken());

        int[] fields = new int[row * col];

        for (int i = 0; i < row; i++) {

            stk = new StringTokenizer(br.readLine(), " ");
            for (int j = 0; j < col; j++) {
                fields[i * col + j] = Integer.parseInt(stk.nextToken());
            }
            
        }


        Arrays.sort(fields);

        int[] result = solution(fields, B);
        System.out.println(result[0] + " " + result[1]);


    }


    public int[] solution(int[] fields, int B) {

        int max = fields[fields.length - 1];
        int min = fields[0];
        //min time and hegiht
        int[] result = new int[2];
        result[0] = 2147483647;

        for (int i = min; i <= max; i++) {
            int remainder = B;
            int time = 0;

            for (int num : fields) {
                //양수라면 퍼내야됨 
                if (num - i > 0) {
                    time += Math.abs(num - i) * 2;
                    remainder += (num - i);
                } else if (num - i < 0) {
                    //음수라면 채워야됨
                    time += (i - num);
                    remainder -= (i - num);
                }
            }
            //인벤토리에 있는 양만 사용 했을 경우임
            if (remainder >= 0) {
                
                //
                if (time <= result[0]) {
                    result[0] = time;
                    result[1] = i;
                }
            }

        }

        return result;
    }
}

결과

 조건 처리를 하려고 노력을 하다 보니까, 소거법적인 방법을 생각하는 유연성이 떨어진 것 같다. 오랜 시간 동안 너무 한 방법으로 풀기 위해 고민하지 않았나 싶다. 같은 시간이라도 조금 더 다양한 접근을 위해 노력해야겠다.


포스팅에 문제가 있거나, 설명이 잘못된 부분 지적 환영합니다.

더 나은 퀄리티의 콘텐츠를 제공할 수 있도록 노력하겠습니다.

읽어주셔서 감사합니다. 

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
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
글 보관함