BOJ 풀이

[BOJ/백준 12100/C++] 2048 (Easy)

Vfly 2023. 3. 21. 17:18

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

 

12100번: 2048 (Easy)

첫째 줄에 보드의 크기 N (1 ≤ N ≤ 20)이 주어진다. 둘째 줄부터 N개의 줄에는 게임판의 초기 상태가 주어진다. 0은 빈 칸을 나타내며, 이외의 값은 모두 블록을 나타낸다. 블록에 쓰여 있는 수는 2

www.acmicpc.net

 

[문제 요약]

- 이 게임에서 한 번의 이동은 보드 위에 있는 전체 블록을 상하좌우 네 방향 중 하나로 이동시키는 것이다. 이때, 같은 값을 갖는 두 블록이 충돌하면 두 블록은 하나로 합쳐지게 된다. 한 번의 이동에서 이미 합쳐진 블록은 또 다른 블록과 다시 합쳐질 수 없다. (실제 게임에서는 이동을 한 번 할 때마다 블록이 추가되지만, 이 문제에서 블록이 추가되는 경우는 없다)

 

- <그림 1>의 경우에서 위로 블록을 이동시키면 <그림 2>의 상태가 된다. 여기서, 왼쪽으로 블록을 이동시키면 <그림 3>의 상태가 된다.

- <그림 4>의 상태에서 블록을 오른쪽으로 이동시키면 <그림 5>가 되고, 여기서 다시 위로 블록을 이동시키면 <그림 6>이 된다. 여기서 오른쪽으로 블록을 이동시켜 <그림 7>을 만들 수 있다.

 

- 이 문제에서 다루는 2048 게임은 보드의 크기가 N×N 이다. 보드의 크기와 보드판의 블록 상태가 주어졌을 때, 최대 5번 이동해서 만들 수 있는 가장 큰 블록의 값을 구하는 프로그램을 작성하시오.

 

[문제 조건]

- 첫째 줄에 보드의 크기 N (1 ≤ N ≤ 20)이 주어진다.

- 둘째 줄부터 N개의 줄에는 게임판의 초기 상태가 주어진다. 0은 빈 칸을 나타내며, 이외의 값은 모두 블록을 나타낸다.

- 블록에 쓰여 있는 수는 2보다 크거나 같고, 1024보다 작거나 같은 2의 제곱꼴이다. 블록은 적어도 하나 주어진다.

- 시간 제한 : 1초

- 메모리 제한 : 512MB

 

[문제 풀이]

이번 문제는 N의 크기도 최대 20이거 움직이는 횟수도 최대 5번밖에 움직이지 않기 때문에 브루트포스 방식으로 해결할 수 있다.

 

필자는 4개의 방향에 대해 보드의 변화를 주는 함수를 각각 만들었고, DFS 방식으로 값을 구하였다.

 

큰 틀은 다음과 같다.

void solve(vector<vector<int>> mp, int depth)
{
	if (depth == 5)
	{
		//
        ...
        //
        return;
	}
	
	vector<vector<int>> tmp = mp;
	left_push(tmp); solve(tmp, depth + 1);

	tmp = mp;
	right_push(tmp); solve(tmp, depth + 1);

	tmp = mp;
	up_push(tmp); solve(tmp, depth + 1);

	tmp = mp;
	down_push(tmp); solve(tmp, depth + 1);
}

모든 방향에 대해 이동시켜보고 5번까지 이동했으면 보드에서 가장 큰 수를 찾아 ans에 갱신해주면 된다.

 

그렇다면 제일 중요한 각 방향에 따른 갱신 함수를 살펴보자.

 

필자는 한 방향(왼쪽으로 밀었을때)에 대해서만 설명하고 나머지는 방향은 맨 밑 코드를 통해 이해해보자.

 

먼저 블럭들을 왼쪽으로 밀게 되면 같은 값을 갖는 블록들은 합쳐질 것이고 그렇지 않은 블록들은 왼쪽으로 이동하게 될 것이다.

 

이때 블록들을 어떤 순서로 왼쪽으로 옮기느냐가 중요하다.

블록을 왼쪽으로 민다고 가정하면 보드상에서 왼쪽에 있는 블록들이 먼저 움직여야 할 것이다.

 

왜냐하면 파란색 블록이 먼저 움직이게 되면 빨간색으로 표시한 부분에 막혀 더 진행하지 않게 된다. 하지만 실제로는 빨간색 부분이 움직이면서 생긴 공간까지 파란색이 이동할 수 있기 때문에 이동하고자 하는 방향에 있는 블록들부터 먼저 움직여야 한다.

 

결론적으로 왼쪽으로 밀면 (왼쪽->오른쪽) 오른쪽으로 밀면 (오른쪽->왼쪽) , 위로 밀면 (위->아래) , 아래로 밀면(아래->위)와 같이 블록들을 움직여줘야 된다.

 

여기서 헷갈리지 않아야 되는 부분이 블록들을 왼쪽에서 오른쪽으로 옮긴다는 의미가 아니라 왼쪽 블록부터 움직인다는 것을 주의하자.

 

또한 블록들을 움직이면서 같은 값을 갖는 블록들은 합쳐지게 되는데, 한 번 합쳐진 블록에 대해서는 더 이상 합쳐지지 않는 다는 것을 주의 해야 된다.

 

만약 예를들어 블록이 ( 2, 2, 4 ) 와 같이 주어져 있고 왼쪽으로 밀었을때 값이 8인 블록이 생기는 것이 아니라 (4, 4)와 같이 블록이 생긴다는 것을 주의해야 한다.

 

이 부분은 각 이동 함수 별로 합체 여부를 체크하는 배열을 하나 두어 관리 하였다.

[이동 함수(왼쪽으로 밀었을 때]

void left_push(vector<vector<int>>& mp)
{
	vector<vector<bool>> ck(n, vector<bool>(n, 0));
	for (int j = 0; j < n; j++)
	{
		for (int i = 0; i < n; i++)
		{
			int value = mp[i][j];
			for (int h = 1; ; h++)
			{
				int nx = j - h;
				if (nx < 0) { mp[i][j] = 0; mp[i][j - (h - 1)] = value; break; }
				if (mp[i][nx] == 0) continue;
				if (mp[i][nx] == value && !ck[i][nx]) { mp[i][nx] = value * 2; mp[i][j] = 0; ck[i][nx] = true;  break; }
				else { mp[i][j] = 0; mp[i][j - (h-1)] = value; break; }
			}
		}
	}
}

다른 방향은 i, j의 시작위치랑 순서등을 잘 조작하면 된다.

ck배열이 합체 여부 판단

 

 

[전체 소스 코드]

#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
#include <map>
#include <string>
#include <set>

#define INF 987654321
#define mod 1000000
#define pii pair<int,int>

using namespace std;

int n;
int ans = -1;

void up_push(vector<vector<int>>& mp)
{
	vector<vector<bool>> ck(n, vector<bool>(n, 0));
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < n; j++)
		{
			int value = mp[i][j];
			for (int h = 1; ; h++)
			{
				int ny = i - h;
				if (ny < 0) { mp[i][j] = 0; mp[i - (h - 1)][j] = value; break; }
				if (mp[ny][j] == 0) continue;
				if (mp[ny][j] == value && !ck[ny][j]) { mp[ny][j] = value * 2; mp[i][j] = 0; ck[ny][j] = true;  break; }
				else { mp[i][j] = 0; mp[i - (h - 1)][j] = value; break; }
			}
		}
	}
}

void down_push(vector<vector<int>>& mp)
{
	vector<vector<bool>> ck(n, vector<bool>(n, 0));
	for (int i = n-1; i >= 0; i--)
	{
		for (int j = 0; j < n; j++)
		{
			int value = mp[i][j];
			for (int h = 1; ; h++)
			{
				int ny = i + h;
				if (ny >= n) { mp[i][j] = 0; mp[i + (h - 1)][j] = value; break; }
				if (mp[ny][j] == 0) continue;
				if (mp[ny][j] == value && !ck[ny][j]) { mp[ny][j] = value * 2; mp[i][j] = 0; ck[ny][j] = true;  break; }
				else { mp[i][j] = 0; mp[i + (h - 1)][j] = value; break; }
			}
		}
	}
}

void left_push(vector<vector<int>>& mp)
{
	vector<vector<bool>> ck(n, vector<bool>(n, 0));
	for (int j = 0; j < n; j++)
	{
		for (int i = 0; i < n; i++)
		{
			int value = mp[i][j];
			for (int h = 1; ; h++)
			{
				int nx = j - h;
				if (nx < 0)
				{
					mp[i][j] = 0; mp[i][j - (h - 1)] = value; break;
				}
				if (mp[i][nx] == 0) continue;
				if (mp[i][nx] == value && !ck[i][nx]) { mp[i][nx] = value * 2; mp[i][j] = 0; ck[i][nx] = true;  break; }
				else { mp[i][j] = 0; mp[i][j - (h-1)] = value; break; }
			}
		}
	}
}

void right_push(vector<vector<int>>& mp)
{
	vector<vector<bool>> ck(n, vector<bool>(n, 0));
	for (int j = n-1; j >= 0; j--)
	{
		for (int i = 0; i < n; i++)
		{
			int value = mp[i][j];
			for (int h = 1; ; h++)
			{
				int nx = j + h;
				if (nx >= n) { mp[i][j] = 0; mp[i][j + (h - 1)] = value; break; }
				if (mp[i][nx] == 0) continue;
				if (mp[i][nx] == value && !ck[i][nx]) { mp[i][nx] = value * 2; mp[i][j] = 0; ck[i][nx] = true; break; }
				else { mp[i][j] = 0; mp[i][j + (h - 1)] = value; break; }
			}
		}
	}
}

void solve(vector<vector<int>> mp, int depth)
{
	if (depth == 5)
	{
		int max_v = -1;
		for (int i = 0; i < n; i++)
		{
			for (int j = 0; j < n; j++)
			{
				max_v = max(max_v, mp[i][j]);
			}
		}
		ans = max(ans, max_v);
		return;
	}
	
	vector<vector<int>> tmp = mp;
	left_push(tmp); solve(tmp, depth + 1);

	tmp = mp;
	right_push(tmp); solve(tmp, depth + 1);

	tmp = mp;
	up_push(tmp); solve(tmp, depth + 1);

	tmp = mp;
	down_push(tmp); solve(tmp, depth + 1);
}

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

	vector<vector<int>> mp;
	cin >> n;
	mp.resize(n); for (int i = 0; i < n; i++) mp[i].resize(n);

	for (int i = 0; i < n; i++)
		for (int j = 0; j < n; j++)
			cin >> mp[i][j];
	
	solve(mp, 0);
	cout << ans;
}