알고리즘/[ADV] 삼성 SW 역량 테스트 A형

[코드트리] 메이즈 러너 (삼성 SW 역량테스트 2023 상반기 오후 1번)

피로물든딸기 2024. 7. 31. 01:24
반응형

삼성 A형 전체 링크

 

참고

- N x N 2차원 배열 뒤집기, 회전하기 (Rotate, Flip 2D Array)

 

https://www.codetree.ai/training-field/frequent-problems/problems/maze-runner

이후 설명 및 입/출력은 링크 참고

 

좌표를 관리하는 구조체를 다음과 같이 선언한다.

기본적으로 2차원 배열의 (r, c)를 위해 사용하지만,

미로를 회전할 때, 영역의 크기size로, player의 탈출 여부escape로 관리한다.

typedef struct st1
{
	int r;
	int c;
	int size; // 회전을 위한 크기
	int escape; // Player 탈출 확인
}RC;

RC PLAYER[10 + 5];
RC EXIT;

 

상, 하, 좌, 우 우선순위로 움직이기 위해 배열을 선언한다.

#define UP (0)
#define DOWN (1)
#define LEFT (2)
#define RIGHT (3)

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

 

배열을 회전하기 위한 함수는 다음과 같다.

void copyMap(int src[MAX][MAX], int dest[MAX][MAX])
{
	for (int i = 0; i < MAX; i++)
		for (int k = 0; k < MAX; k++)
			src[i][k] = dest[i][k];
}

void rotate(int map[MAX][MAX], int sr, int sc, int size)
{
	int temp[MAX][MAX] = { 0 };

	copyMap(temp, map);

	for (int r = 0; r < size; r++)
		for (int c = 0; c < size; c++)
			map[r + sr][c + sc] = temp[size - 1 - c + sr][r + sc];
}

 

input은 다음과 같다.

MAP에는 벽만 관리하고, 참가자와 출구는 따로 관리한다.

필요한 경우MAP을 임시로 복사하고, 복사된 MAP에 참가자와 출구를 표시한다.

void input()
{
	scanf("%d %d %d", &N, &M, &K);

	for (int r = 1; r <= N; r++)
		for (int c = 1; c <= N; c++)
			scanf("%d", &MAP[r][c]);

	for (int m = 0; m < M; m++)
	{
		scanf("%d %d", &PLAYER[m].r, &PLAYER[m].c);	
		PLAYER[m].escape = 0;
	}

	scanf("%d %d", &EXIT.r, &EXIT.c);
}

 

main은 다음과 같다.

1) move()에서 step 횟수를 구한 후, sum에서 누적한다.

2)  allExitCheck()에서 모든 사람이 탈출했는지 체크한다. (true면 종료)

3) 미로를 회전한다.

int main(void)
{
	// scanf("%d", &T);
	T = 1;
	for (int tc = 1; tc <= T; tc++)
	{
		input();

		int sum = 0;
		for (int k = 0; k < K; k++)
		{
			int step = move();

			sum += step;

			if (allExitCheck() == true) break;

			rotateMaze();
		}

		printf("%d\n", sum);
		printf("%d %d\n", EXIT.r, EXIT.c);
	}

	return 0;
}

 

모든 사람이 탈출했는지 여부는 다음과 같이 간단히 확인할 수 있다.

int allExitCheck()
{
	for (int p = 0; p < M; p++)
		if (PLAYER[p].escape == 0) return 0;

	return 1;
}

플레이어 이동

 

move는 다음과 같다.

 

탈출하지 않은 모든 Player에 대해 

1) 네 방향으로 움직일 경우의 좌표를 미리 구한다.

2) EXIT 좌표와 비교하여 상하 / 좌우로 움직일 수 있는지 먼저 확인한다. (checkRow, checkCol)

3) 상하 / 좌우 우선순위로 갈 수 있는 방향을 direction 변수에 저장한다. (with 벽 체크)

4) direction에 값이 있는 경우 Player의 좌표를 변경하고 step을 1 증가시킨다.

int move()
{
	int step;
	RC next;

	step = 0;
	for (int p = 0; p < M; p++)
	{
		if (PLAYER[p].escape == ESCAPE) continue;

		// 4방향 좌표 할당
		int nextR[4] = { 0 };
		int nextC[4] = { 0 };

		for (int i = 0; i < 4; i++)
		{
			nextR[i] = PLAYER[p].r + dr[i];
			nextC[i] = PLAYER[p].c + dc[i];
		}

		int direction = -1;
		
		// 상하로 움직이기		
		int upDown = checkRow(PLAYER[p]);
		int leftRight = checkCol(PLAYER[p]);

		// EXIT 방향으로 이동하므로 좌표를 벗어나지 않음.
		if (upDown > 0 && MAP[nextR[UP]][nextC[UP]] == 0) direction = UP;
		else if (upDown < 0 && MAP[nextR[DOWN]][nextC[DOWN]] == 0) direction = DOWN;
		else if (leftRight > 0 && MAP[nextR[LEFT]][nextC[LEFT]] == 0) direction = LEFT;
		else if (leftRight < 0 && MAP[nextR[RIGHT]][nextC[RIGHT]] == 0) direction = RIGHT;

		if (direction == -1) continue;

		PLAYER[p].r = PLAYER[p].r + dr[direction];
		PLAYER[p].c = PLAYER[p].c + dc[direction];
		
		if (PLAYER[p].r == EXIT.r && PLAYER[p].c == EXIT.c) PLAYER[p].escape = ESCAPE;

		step++;
	}

	return step;
}

 

상하 / 좌우로 갈 수 있는지 판단하는 함수는 다음과 같다.

int checkRow(RC player)
{
	int er = EXIT.r;
	int pr = player.r;
	
	// er > pr -> exit는 아래에
	// er = pr -> 같은 행, 상하로 움직일 수 없음.
	// er < pr -> exit는 위에
	return er - pr;
}

int checkCol(RC player)
{
	int ec = EXIT.c;
	int pc = player.c;

	// pc > ec -> exit는 왼쪽에
	// ec = pc -> 같은 열, 좌우로 움직일 수 없음.
	// pc < ec -> exit는 오른쪽에
	return pc - ec;
}

미로 회전

 

rotateMaze는 다음과 같다.

1) getRotateInfo에서 한 명 이상 참가자출구를 포함한 가장 작은 정사각형을 찾는다.

이때 return 되는 값 = rotateInfo에서 정사각형의 오른쪽 위 좌표와, size를 알 수 있다.

 

2) 해당되는 영역 90도 회전한다. 

3) EXIT 좌표 90도 회전한다.

4) 해당되는 영역에 포함된 Player를 90도 회전한다.

5) 해당되는 영역에 포함된 벽의 내구도를 1씩 깎는다.

void rotateMaze()
{
	RC rotateInfo = getRotateInfo();
	
	rotate(MAP, rotateInfo.r, rotateInfo.c, rotateInfo.size);

	EXIT = rotateRC(EXIT, rotateInfo);
		
	for (int p = 0; p < M; p++)
	{
		if (PLAYER[p].escape == ESCAPE) continue;

		if (isBoundary(PLAYER[p], rotateInfo) == 0) continue;

		PLAYER[p] = rotateRC(PLAYER[p], rotateInfo);	
	}

	wallDown(rotateInfo);
}

 

rotateInfo는 다음과 같이 구할 수 있다.

가장 작은 size = 2 부터, 작고, 작은 순서대로 정사각형 영역을 만든다.

그리고 MAPtmpMAP에 복사하고 tmpMAPPlayerEXIT 좌표를 추가한다.

이때, EXIT = -1 (EXIT_CHECK)로 표시하고, Player는 10보다 큰 값 (+ MARK = 10)으로 표기하여 벽과 구분하였다.

(탈출에 성공한 Player는 표시하지 않는다.)

그리고 rotateCheck에서 tmpMAP과 주어진 영역에 EXIT와 Player의 좌표가 있는지 확인한다.

RC getRotateInfo()
{
	RC ret = { 0 };
	for (int size = 2; size <= N; size++)
	{
		for (int r = 1; r <= N - size + 1; r++)
		{
			for (int c = 1; c <= N - size + 1; c++)
			{
				int tmpMAP[MAX][MAX] = { 0 };
				
				copyMap(tmpMAP, MAP);

				for (int p = 0; p < M; p++)
				{
					if (PLAYER[p].escape == ESCAPE) continue;
					tmpMAP[PLAYER[p].r][PLAYER[p].c] = p + MARK;
				}

				tmpMAP[EXIT.r][EXIT.c] = EXIT_CHECK;
				
				if (rotateCheck(tmpMAP, r, c, size) != 0)
				{					
					ret.r = r;
					ret.c = c;
					ret.size = size;
					
					return ret;
				}
			}
		}
	}
}

 

rotateCheck는 다음과 같다.

참고로 Player같은 좌표에 있더라도 한 명 이상이기만 하면 회전이 가능하다.

따라서 getRotateInfo에서 tmpMAP[PLAYER[p].r][PLAYER[p].c] = p + MARK; 로 충분하다.

int rotateCheck(int map[MAX][MAX], int sr, int sc, int size)
{
	int exitCheck;
	int playerCheck;

	exitCheck = playerCheck = 0;
	for (int r = sr; r < sr + size; r++)
	{
		for (int c = sc; c < sc + size; c++)
		{
			if (map[r][c] >= MARK) playerCheck++;
			else if (map[r][c] == EXIT_CHECK) exitCheck = 1;
		}
	}

	return exitCheck && playerCheck;
}

 

MAP에는 출구와 참가자가 없으므로, 따로 좌표를 구해야 한다.

하나의 좌표여러 참가자가 있을 수 있기 때문에 좌표를 구분하였다.

rotateRC 함수에서 좌표 rc와 rotateInfo를 이용해 좌표를 변경한다.

2차원 빈 배열에서 (출구 또는 참가자의) 좌표를 1로 표시하고, rotate를 한 후, 다시 1의 좌표를 찾으면 된다.

RC rotateRC(RC rc, RC rotateInfo)
{
	RC ret = { 0 };
	int tmpMAP[MAX][MAX] = { 0 };

	tmpMAP[rc.r][rc.c] = 1;

	rotate(tmpMAP, rotateInfo.r, rotateInfo.c, rotateInfo.size);

	for (int r = 1; r <= N; r++)
	{
		for (int c = 1; c <= N; c++)
		{
			if (tmpMAP[r][c] == 1)
			{
				ret.r = r;
				ret.c = c;

				return ret;
			}
		}
	}
			
	ret.r = ret.c = -1; // error

	return ret;
}

 

Player의 경우, 탈출하지 않고 영역에 있는 경우rotate한 좌표를 구한다.

void rotateMaze()
{
	...
	EXIT = rotateRC(EXIT, rotateInfo);
		
	for (int p = 0; p < M; p++)
	{
		if (PLAYER[p].escape == ESCAPE) continue;

		if (isBoundary(PLAYER[p], rotateInfo) == 0) continue;

		PLAYER[p] = rotateRC(PLAYER[p], rotateInfo);	
	}
}

 

영역에 있는지는 다음과 같이 확인할 수 있다.

int isBoundary(RC rc, RC rotateInfo)
{
	int sr = rotateInfo.r;
	int er = rotateInfo.r + rotateInfo.size;
	int sc = rotateInfo.c;
	int ec = rotateInfo.c + rotateInfo.size;

	return (sr <= rc.r && rc.r < er && sc <= rc.c && rc.c < ec);
}

 

마지막으로 해당되는 영역벽의 내구도를 감소한다.

void wallDown(RC rotateInfo)
{
	for (int r = 1; r <= N; r++)
	{
		for (int c = 1; c <= N; c++)
		{
			if (MAP[r][c] == 0) continue;

			RC rc = { 0 };
			
			rc.r = r; 
			rc.c = c;
			
			if (isBoundary(rc, rotateInfo) == 0) continue;
			
			MAP[r][c]--;
		}
	}
}

전체 코드는 다음과 같다.

#include <stdio.h>

#define MAX (10 + 5)

#define MARK (10)
#define EXIT_CHECK (-1)
#define ESCAPE (10)

int T;
int N, M, K;

int MAP[MAX][MAX];

typedef struct st1
{
	int r;
	int c;
	int size; // 회전을 위한 크기
	int escape; // Player 탈출 확인
}RC;

RC PLAYER[10 + 5];
RC EXIT;

#define UP (0)
#define DOWN (1)
#define LEFT (2)
#define RIGHT (3)

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

void input()
{
	scanf("%d %d %d", &N, &M, &K);

	for (int r = 1; r <= N; r++)
		for (int c = 1; c <= N; c++)
			scanf("%d", &MAP[r][c]);

	for (int m = 0; m < M; m++)
	{
		scanf("%d %d", &PLAYER[m].r, &PLAYER[m].c);	
		PLAYER[m].escape = 0;
	}

	scanf("%d %d", &EXIT.r, &EXIT.c);
}

void printMap()
{
	int temp[MAX][MAX];
	for (int r = 1; r <= N; r++)
		for (int c = 1; c <= N; c++)
			temp[r][c] = MAP[r][c];

	for (int p = 0; p < M; p++)
	{
		printf("%d : %d, %d (esc : %d)\n", p, PLAYER[p].r, PLAYER[p].c, PLAYER[p].escape);
		if(PLAYER[p].escape == 0) temp[PLAYER[p].r][PLAYER[p].c] = p + MARK;
	}

	putchar('\n');

	temp[EXIT.r][EXIT.c] = EXIT_CHECK;

	for (int r = 1; r <= N; r++)
	{
		for (int c = 1; c <= N; c++)
			printf("%2d ", temp[r][c]);
		putchar('\n');
	}	
	printf("============================\n");
}

void printMap(int map[MAX][MAX])
{
	for (int r = 1; r <= N; r++)
	{
		for (int c = 1; c <= N; c++)
			printf("%2d ", map[r][c]);
		putchar('\n');
	}
	putchar('\n');
}

void copyMap(int src[MAX][MAX], int dest[MAX][MAX])
{
	for (int i = 0; i < MAX; i++)
		for (int k = 0; k < MAX; k++)
			src[i][k] = dest[i][k];
}

void rotate(int map[MAX][MAX], int sr, int sc, int size)
{
	int temp[MAX][MAX] = { 0 };

	copyMap(temp, map);

	for (int r = 0; r < size; r++)
		for (int c = 0; c < size; c++)
			map[r + sr][c + sc] = temp[size - 1 - c + sr][r + sc];
}

int checkRow(RC player)
{
	int er = EXIT.r;
	int pr = player.r;
	
	// er > pr -> exit는 아래에
	// er = pr -> 같은 행, 상하로 움직일 수 없음.
	// er < pr -> exit는 위에
	return er - pr;
}

int checkCol(RC player)
{
	int ec = EXIT.c;
	int pc = player.c;

	// pc > ec -> exit는 왼쪽에
	// ec = pc -> 같은 열, 좌우로 움직일 수 없음.
	// pc < ec -> exit는 오른쪽에
	return pc - ec;
}

int move()
{
	int step;
	RC next;

	step = 0;
	for (int p = 0; p < M; p++)
	{
		if (PLAYER[p].escape == ESCAPE) continue;

		// 4방향 좌표 할당
		int nextR[4] = { 0 };
		int nextC[4] = { 0 };

		for (int i = 0; i < 4; i++)
		{
			nextR[i] = PLAYER[p].r + dr[i];
			nextC[i] = PLAYER[p].c + dc[i];
		}

		int direction = -1;
		
		// 상하로 움직이기		
		int upDown = checkRow(PLAYER[p]);
		int leftRight = checkCol(PLAYER[p]);

		// EXIT 방향으로 이동하므로 좌표를 벗어나지 않음.
		if (upDown > 0 && MAP[nextR[UP]][nextC[UP]] == 0) direction = UP;
		else if (upDown < 0 && MAP[nextR[DOWN]][nextC[DOWN]] == 0) direction = DOWN;
		else if (leftRight > 0 && MAP[nextR[LEFT]][nextC[LEFT]] == 0) direction = LEFT;
		else if (leftRight < 0 && MAP[nextR[RIGHT]][nextC[RIGHT]] == 0) direction = RIGHT;

		if (direction == -1) continue;

		PLAYER[p].r = PLAYER[p].r + dr[direction];
		PLAYER[p].c = PLAYER[p].c + dc[direction];
		
		if (PLAYER[p].r == EXIT.r && PLAYER[p].c == EXIT.c) PLAYER[p].escape = ESCAPE;

		step++;
	}

	return step;
}

int allExitCheck()
{
	for (int p = 0; p < M; p++)
		if (PLAYER[p].escape == 0) return 0;

	return 1;
}

int rotateCheck(int map[MAX][MAX], int sr, int sc, int size)
{
	int exitCheck;
	int playerCheck;

	exitCheck = playerCheck = 0;
	for (int r = sr; r < sr + size; r++)
	{
		for (int c = sc; c < sc + size; c++)
		{
			if (map[r][c] >= MARK) playerCheck++;
			else if (map[r][c] == EXIT_CHECK) exitCheck = 1;
		}
	}

	return exitCheck && playerCheck;
}

RC getRotateInfo()
{
	RC ret = { 0 };
	for (int size = 2; size <= N; size++)
	{
		for (int r = 1; r <= N - size + 1; r++)
		{
			for (int c = 1; c <= N - size + 1; c++)
			{
				int tmpMAP[MAX][MAX] = { 0 };
				
				copyMap(tmpMAP, MAP);

				for (int p = 0; p < M; p++)
				{
					if (PLAYER[p].escape == ESCAPE) continue;
					tmpMAP[PLAYER[p].r][PLAYER[p].c] = p + MARK;
				}

				tmpMAP[EXIT.r][EXIT.c] = EXIT_CHECK;
				
				if (rotateCheck(tmpMAP, r, c, size) != 0)
				{					
					ret.r = r;
					ret.c = c;
					ret.size = size;
					
					return ret;
				}
			}
		}
	}
}

RC rotateRC(RC rc, RC rotateInfo)
{
	RC ret = { 0 };
	int tmpMAP[MAX][MAX] = { 0 };

	tmpMAP[rc.r][rc.c] = 1;

	rotate(tmpMAP, rotateInfo.r, rotateInfo.c, rotateInfo.size);

	for (int r = 1; r <= N; r++)
	{
		for (int c = 1; c <= N; c++)
		{
			if (tmpMAP[r][c] == 1)
			{
				ret.r = r;
				ret.c = c;

				return ret;
			}
		}
	}
			
	ret.r = ret.c = -1; // error

	return ret;
}

int isBoundary(RC rc, RC rotateInfo)
{
	int sr = rotateInfo.r;
	int er = rotateInfo.r + rotateInfo.size;
	int sc = rotateInfo.c;
	int ec = rotateInfo.c + rotateInfo.size;

	return (sr <= rc.r && rc.r < er && sc <= rc.c && rc.c < ec);
}

void wallDown(RC rotateInfo)
{
	for (int r = 1; r <= N; r++)
	{
		for (int c = 1; c <= N; c++)
		{
			if (MAP[r][c] == 0) continue;

			RC rc = { 0 };
			
			rc.r = r; 
			rc.c = c;
			
			if (isBoundary(rc, rotateInfo) == 0) continue;
			
			MAP[r][c]--;
		}
	}
}

void rotateMaze()
{
	RC rotateInfo = getRotateInfo();
	
	rotate(MAP, rotateInfo.r, rotateInfo.c, rotateInfo.size);

	EXIT = rotateRC(EXIT, rotateInfo);
		
	for (int p = 0; p < M; p++)
	{
		if (PLAYER[p].escape == ESCAPE) continue;

		if (isBoundary(PLAYER[p], rotateInfo) == 0) continue;

		PLAYER[p] = rotateRC(PLAYER[p], rotateInfo);	
	}

	wallDown(rotateInfo);
}

int main(void)
{
	// scanf("%d", &T);
	T = 1;
	for (int tc = 1; tc <= T; tc++)
	{
		input();

		int sum = 0;
		for (int k = 0; k < K; k++)
		{
			int step = move();

			sum += step;

			if (allExitCheck() == true) break;

			rotateMaze();
		}

		printf("%d\n", sum);
		printf("%d %d\n", EXIT.r, EXIT.c);
	}

	return 0;
}
반응형