컴퓨터기본/문제풀이

[백준] 12100번: 2048(Easy)

차가운오미자 2021. 11. 26. 23:47

 

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

 

12100번: 2048 (Easy)

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

www.acmicpc.net

 

문제 이해

아주 배열을 세심하게 다뤄야 하는 문제인데, 심지어 예제가 하나밖에 없다.

그냥 본인이 여러 숫자를 넣어가면서 생각대로 잘 맵이 이동하는지를 살피는 수밖에 없다.

 

일단 처음 문제를 읽으면 최대 5번 이동한다고 했으니 5번의 방향을 정하는 DFS를 돌려야할 것 같다는 생각이 든다.

근데, 최대 5번이니까 1번 움직였을 때 최댓값이 나올 수도 있고 2번 움직였을 때 최댓값이 나올 수도 있고... 그렇다.

그래서 이동 한 번 할 때마다 최댓값을 체크해줘야 한다.

(그냥 더이상 값 변동이 없을 때의 값을 체크해도 될 것 같다.)

 

아무튼 나는 그냥 한번 기울일 때마다 max값을 체크해주긴 했다.

 

DFS()

DFS 재귀파트가 조금 헷갈리는게, 매 단계마다 map을 바꿔주기 때문에 원상복구 시키는 게 좀 헷갈렸다. (사실 내가 코드를 누더기처럼 짜서 그럴 수도 있지만)

void DFS(int cnt) {
	// 종료
	if (cnt == 5) return;
	int tmpmap[MAXN + 1][MAXN + 1];
	memcpy(tmpmap, map, sizeof(map));
	// 재귀
	for (int i = 0; i < 4; i++) {
		if (Move_Blocks(i) == 0) {
			memcpy(map, tmpmap, sizeof(map));
			continue;
		}
		DFS(cnt + 1);
		memcpy(map, tmpmap, sizeof(map));
	}
}

처음에 간과했던 부분은 Move_Blocks(i)==0 일때 그냥 continue를 해줬다는 점이다.

그렇게 하면 같은 단계에서 다른 방향으로 기울일 때, 예를 들어 cnt == 1일 때 위쪽으로 한 번 기울이고, 아래쪽으로 기울일 때, 위쪽으로 기울인 map상태로 넘어가서 문제가 된다. 그래서 continue 전에도 한 번 원래 맵을 복붙해줘야 한다

 

결국 원상복귀가 조금 헷갈렸다고 볼 수 있다.

 

 

네 방향으로 기울이는 과정은 네 방향이 다 비슷하다. 좌표들만 조금 주의해서 주면 되는데, 원리를 상세히 설명하기 위해 왼쪽으로 기울이는 과정을 기준으로 설명한다.

Move_Blocks_Left()

일단 나는 숫자가 같은 애들을 합치고, 그러고서 모든 숫자가 왼쪽으로 쏠릴 수 있게 몰아주는 과정을 거쳤다.

이렇게 하면 한 번 더해진 애는 패스할 수 있다.

근데 방향을 조금 주의해야할 것이, 이 함수는 왼쪽으로 기울이는 거니까 왼쪽 끝부터 시작하고, 오른쪽 기울이는 함수는 오른쪽, 위쪽으로 기울이는 함수는 위쪽에서 시작해야 한다. 일단 그래야 안헷갈리고, 한 번 더한 애를 패스할 수 있다.

이 두 과정은 모두 각 행 별로 수행해준다. 설명은 위의 예시를 보면서 설명한다.

1. 숫자가 같은 애들 합치기

  • map[0][0]번엔 2가 있다. 이걸 기준으로 오른쪽으로 가면서 확인한다.
  • map[0][1]번은 0이다. 0이면 continue 해서 그 뒤에 숫자들을 확인한다.
  • map[0][2] c==2번은 2이다. 기준점이었던 map[0][0]과 같다. 그럼 합칠 수 있다.
  • map[0][0] 을 두 배를 해주고, map[0][2]는 지워버린다. 그리고 break해서 이 기준점 (map[0][0])에 대한 탐색을 종료한다
  • 그럼 map[0][0]에는 4가 담기고, 기준점은 map[0][1]이 된다. 그럼 이제 map[0][0] 은 이 과정에서 다시 건들지 않게 된다. (== 한 번 합쳐진 애는 같은 과정에서 또 합치지 않는다.)
  • 새로운 기준점 map[0][1]에 대해 오른쪽으로 가면서 같은 과정을 밟는다.
  • for (int j = 0; j < N-1; j++) {
      for (int k = j + 1; k < N; k++) {
        if (map[i][k] == 0) continue;
        if (map[i][k] != map[i][j]) break;
        map[i][j] += map[i][k];
        map[i][k] = 0;
        moved = 1;
        break;
      }
    }

이렇게 하면 숫자가 같은 애들은 다 왼쪽 기준으로 합쳐진다.

2. 공백 지우기

이제 왼쪽으로 모든 숫자가 기울 수 있도록 한다.

또 다시 기준점 map[0][0]부터 시작한다.

  • map[0][0]은 4이다. 0이 아니니 패스한다. (continue)
  • map[0][1]은 0이다. 0이니까 뒤에 0이 아닌 숫자가 있으면 swap해준다. 어차피 뒤에 없으니까 패스된다.
  • 이렇게 첫 번째 행, 두 번째 행을 처리했다고 치고, 세 번째 행을 살펴보자
  • map[2][0]은 0이다. 그럼 오른쪽부터 또 탐색하면서 0이 아닌 값을 찾는다. map[2][1]이 16이다. 그럼 map[2][0]으로 이 값을 옮겨준다. 그럼 16 0 0 이 되었다.
  • 다음 기준점 map[2][1]을 잡고 또 map[2][2]부터 살핀다. 더이상 숫자가 없어서 패스된다. 
  • // 빈 칸 당긴다
    for (int j = 0; j < N; j++) {
      if (map[i][j] == 0) { 
        for (int k = j + 1; k < N; k++) {
          if (map[i][k] == 0) continue;
          map[i][j] = map[i][k];
          map[i][k] = 0;
          moved = 1;
          break;
      }
    }

이런 식으로 처리하면 왼쪽 정렬을 할 수 있다. 

여기에 moved라는 변수가 있는데, map에 위치 변동이 생기지 않으면 moved가 0으로 유지되고, 더이상 이 맵에 대해 기울이기를 반복해봤자 다른 값이 나오지 않는다는 뜻이다.

int Move_Blocks_Left(void) {
	int max = 0;
	int moved = 0;
	for (int i = 0; i < N; i++) {
		// 합칠 애들을 찾는다
		for (int j = 0; j < N-1; j++) {
			for (int k = j + 1; k < N; k++) {
				if (map[i][k] == 0) continue;
				if (map[i][k] != map[i][j]) break;
				map[i][j] += map[i][k];
				map[i][k] = 0;
				moved = 1;
				break;
			}
		}
		// 빈 칸 당긴다
		for (int j = 0; j < N; j++) {
			if (map[i][j] == 0) { 
				for (int k = j + 1; k < N; k++) {
					if (map[i][k] == 0) continue;
					map[i][j] = map[i][k];
					map[i][k] = 0;
					moved = 1;
					break;
				}
			}
			if (map[i][j] > max) max = map[i][j];
		}
	}
	if (max > ans) ans = max;
	return moved;
}

 

전체 코드

#include <stdio.h>
#include <string.h>
#define MAXN 20
#define UP 0
#define DOWN 1
#define LEFT 2
#define RIGHT 3

int N;
int map[MAXN+1][MAXN+1];

int sel[5 + 1];
int ans;

int dy[] = { -1, 1, 0, 0 }; // 상하좌우
int dx[] = { 0, 0, -1, 1 }; 

void Get_Input(void) {
	scanf("%d", &N);
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < N; j++) {
			scanf("%d", &map[i][j]);
		}
	}
}

int Move_Blocks_Up(void) {
	int moved = 0;
	int max = 0;
	for (int c = 0; c < N; c++) {
		// 합칠 애들을 찾는다
		for (int r = 0; r < N-1; r++) {
			for (int k = r + 1; k < N; k++) {
				if (map[k][c] == 0) continue;
				if (map[k][c] != map[r][c]) break;
				map[r][c] += map[k][c];
				map[k][c] = 0;
				moved = 1;
				break;
			}
		}
		// 빈 칸 당긴다
		for (int r = 0; r < N; r++) {
			if (map[r][c] == 0) {
				for (int k = r + 1; k < N; k++) {
					if (map[k][c] == 0) continue;
					map[r][c] = map[k][c];
					map[k][c] = 0;
					moved = 1;
					break;
				}
			}
			if (map[r][c] > max) max = map[r][c];
		}
	}
	if (max > ans) ans = max;
	return moved;
}

int Move_Blocks_Down(void) {
	int moved = 0;
	int max = 0;
	for (int c = 0; c < N; c++) {
		// 합칠 애들을 찾는다
		for (int r = N-1; r > 0; r--) {
			for (int k = r - 1; k >=0; k--) {
				if (map[k][c] == 0) continue;
				if (map[k][c] != map[r][c]) break;
				map[r][c] += map[k][c];
				map[k][c] = 0;
				moved = 1;
				break;
			}
		}
		// 빈 칸 당긴다
		for (int r = N-1; r >0; r--) {
			if (map[r][c] == 0) {
				for (int k = r - 1; k >= 0; k--) {
					if (map[k][c] == 0) continue;
					map[r][c] = map[k][c];
					map[k][c] = 0;
					moved = 1;
					break;
				}
			}
			if (map[r][c] > max) max = map[r][c];
		}
	}
	if (max > ans) ans = max;
	return moved;
}

int Move_Blocks_Left(void) {
	int max = 0;
	int moved = 0;
	for (int i = 0; i < N; i++) {
		// 합칠 애들을 찾는다
		for (int j = 0; j < N-1; j++) {
			for (int k = j + 1; k < N; k++) {
				if (map[i][k] == 0) continue;
				if (map[i][k] != map[i][j]) break;
				map[i][j] += map[i][k];
				map[i][k] = 0;
				moved = 1;
				break;
			}
		}
		// 빈 칸 당긴다
		for (int j = 0; j < N; j++) {
			if (map[i][j] == 0) { 
				for (int k = j + 1; k < N; k++) {
					if (map[i][k] == 0) continue;
					map[i][j] = map[i][k];
					map[i][k] = 0;
					moved = 1;
					break;
				}
			}
			if (map[i][j] > max) max = map[i][j];
		}
	}
	if (max > ans) ans = max;
	return moved;
}


int Move_Blocks_Right(void) {
	int max = 0;
	int moved = 0;
	for (int i = 0; i < N; i++) { // 행
		// 합칠 애들을 찾는다
		for (int j = N-1; j > 0; j--) {
			for (int k = j-1; k >=0; k--) {
				if (map[i][k] == 0) continue;
				if (map[i][k] != map[i][j]) break;
				map[i][j] += map[i][k];
				map[i][k] = 0;
				moved = 1;
				break;
			}
		}
		// 빈 칸 당긴다
		for (int j = N-1; j >= 0; j--) {
			if (map[i][j] == 0) {
				for (int k = j - 1; k >=0; k--) {
					if (map[i][k] == 0) continue;
					map[i][j] = map[i][k];
					map[i][k] = 0;
					moved = 1;
					break;
				}
			}
			if (map[i][j] > max) max = map[i][j];
		}
	}
	if (max > ans) ans = max;
	return moved;
}

int Move_Blocks(int dir) {
	// map을 dir 방향으로 기울인다
	int res = 0;
	switch (dir) {
	case UP:
		res = Move_Blocks_Up();
		break;
	case DOWN:
		res = Move_Blocks_Down();
		break;
	case LEFT:
		res = Move_Blocks_Left();
		break;
	case RIGHT:
		res = Move_Blocks_Right();
		break;
	}
	return res;
}

void DFS(int cnt) {
	// 종료
	if (cnt == 5) return;
	int tmpmap[MAXN + 1][MAXN + 1];
	memcpy(tmpmap, map, sizeof(map));
	// 재귀
	for (int i = 0; i < 4; i++) {
		if (Move_Blocks(i) == 0) {
			memcpy(map, tmpmap, sizeof(map));
			continue;
		}
		DFS(cnt + 1);
		memcpy(map, tmpmap, sizeof(map));
	}
}

int main(void) {
	Get_Input();
	DFS(0);
	printf("%d", ans);
	return 0;
}

'컴퓨터기본 > 문제풀이' 카테고리의 다른 글

[백준] 17135번: 캐슬 디펜스  (0) 2021.11.28
[백준] 17136번: 색종이 붙이기  (0) 2021.11.28
[백준] 3190번: 뱀  (0) 2021.11.26
[백준] 14499번: 주사위 굴리기  (0) 2021.11.26
[백준] 14500번: 테트로미노  (0) 2021.11.25