코딩테스트/백준

[백준 C++] 18111. 마인크래프트

tkxx_ls 2023. 5. 13. 22:59

문제 링크


 

18111번: 마인크래프트

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

www.acmicpc.net

접근 방법


  1. 어떤 알고리즘을 적용시켜야 할지 모르겠다면 우선 브루트 포스로 구현합니다.
  2. 무엇을 기준으로 반복문을 돌려야 시간을 줄일 수 있을지 생각해 봅니다.

소스 코드


#include <iostream>
#include <vector>

using namespace std;

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

	vector<vector<int>> land;
	int maxHeigt = 0, minTime = 128000000, Time, n, m, b, tmp, cur, start = 256, end = 0;
	
	cin >> n >> m >> b;

	land.resize(n);
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < m; j++)
		{
			cin >> tmp;
            
			if (tmp > end)
				end = tmp;
                
			if (tmp < start)
				start = tmp;
                
			land[i].emplace_back(tmp);
		}
	}
    
	cur = start; // cur은 땅 고르기를 할 기준 높이입니다.
	while (cur <= end) // 땅 고르기 작업을 start부터 end까지 해봅니다.
	{
    
		tmp = b; // tmp에는 현재 인벤토리에 들어있는 블록 수를 넣어줍니다.
		Time = 0;
        
		for (int j = 0; j < n; j++)
		{
			for (int k = 0; k < m; k++)
			{
				if (land[j][k] > cur) // 기준 높이보다 크다면 땅을 파줍니다.
				{
					Time += (land[j][k] - cur) << 1; // 제거한 블록 수의 2배가 걸린 시간입니다.
					tmp += (land[j][k] - cur); // 판 블록은 다시 사용할 수 있으므로 넣어줍니다.
				}

				else if (land[j][k] < cur) // 기준 높이보다 낮다면 블록을 쌓아줍니다.
				{
					Time += cur - land[j][k];
					tmp -= (cur - land[j][k]); // 사용한 블록만큼 빼줍니다.
				}
			}
		}
        
		if (tmp >= 0) // 남은 블록 수가 음수이면 cur 높이로 땅고르기를 못한다는 뜻이므로 무시합니다.
		{
			if (minTime > Time) 최소시간보다 현재 걸린 시간이 더 적을 때
			{
				maxHeigt = cur;
				minTime = Time;
			}

			else if (minTime == Time) // 최소시간과 걸린시간이 같은데 현재높이가 더 높을 때
				if (cur > maxHeigt)
					maxHeigt = cur;
		}
		cur++;
	}
	cout << minTime << ' ' << maxHeigt;

	return 0;
}

코드 설명


변수

maxHeight는 출력해야 할 높이, minTime은 출력해야 할 시간입니다.

cur은 지금 평탄화할 땅의 높이, Time은 cur로 땅 고르기를 했을 때 걸린 시간입니다.

land는 땅의 높이를 저장하는 이차원 벡터입니다.

 

코드

먼저 땅의 높이를 입력받으면서 가장 낮은 땅의 높이와 가장 높은 땅의 높이를 찾습니다. 

cur을 start부터 end까지 1씩 증가시키면서 cur 높이로 땅 고르기 작업을 진행합니다.

cur보다 땅의 높이가 낮으면 블록을 쌓고, 높이가 높으면 땅을 파고 인벤토리에 블록을 넣습니다.

해당 작업을 했을 때 인벤토리에 들어있는 블록의 수가 음수이면, 해당 작업을 못한다는 뜻이므로 시간을 비교할 필요가 없습니다.

인벤토리에 들어있는 블록 수가 0 이상일 때, 작업이 최소시간보다 적게 걸렸는지 확인합니다. 

만약 걸린 시간이 최소시간과 같을 경우 최대높이를 비교합니다. 

 

개선

n * m의 땅의 정보로 이차원 배열을 만드는 것보다 0 ~ 256의 땅의 높이의 기수정렬로 접근하는 방법이 훨씬 빠릅니다.

n * m으로 반복문을 돌렸을 때 최악은 250000번이지만 높이로 돌렸을 경우 257입니다.

백준에 예제 입력 1을 예로 들면 n * m으로 반복문을 돌리면 12번씩 돌려야 하지만, 높이로 접근하게 되면 0과 1 밖에 없으니 반복문을 2번만 돌리면 됩니다.

또한 코드 17번째 줄에서 입력받을 때 이중 for문을 사용하는데 for문 한 번으로 바꿉니다.

start와 end를 17번째 줄 for문에서 구하는데 n*m번 비교하는것 보다 더 나은 방법으로 바꿔줍니다.

46번째 줄과 52번째 줄에서 Time을 n*m번 연산하는데 사용한 블록과 파낸 블록만 세서 Time을 구하는 방법으로 접근합니다.

인벤토리에 남은 블록 수가 음수가 되는 시점에서 그 후의 높이로 땅 고르기를 못하므로 반복문을 종료시킵니다.

개선된 코드


#include <iostream>

using namespace std;

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

	int maxHeigt = 0, minTime = 128000000, Time, n, m, b, tmp, start = 256, end = 0;
	int used, earned, cur = 0, height, land[257] = { 0 };
	
	cin >> n >> m >> b;

	n *= m;

	for (int i = 0; i < n; i++)
	{
		cin >> tmp;
		if (tmp > end)
			end = tmp;
		if (tmp < start)
			start = tmp;
		land[tmp]++;
	}

	height = start;

	while (height <= end)
	{
		used = earned = 0;
		cur = start;

		while (cur != height)
		{
			used += (height - cur) * land[cur];
			++cur;
		}
		
		while (++cur <= end)
			earned += (cur - height) * land[cur];

		if (earned + b < used)
			break;
		Time = (earned << 1) + used;
        
		if (minTime >= Time)
		{
			maxHeigt = height;
			minTime = Time;
		}
		++height;
	}
	cout << minTime << ' ' << maxHeigt;

	return 0;
}

 

 

처음 작성한 코드와 개선된 코드의 결과입니다.

실행 시간 차이가 많이 나는 것을 확인할 수 있습니다.