본문 바로가기

PS/CodeTree

[기출문제] 마법의 숲 탐색

코드트리 기출문제에 있는 문제를 풀었다. 왜냐하면, 극한의 구현 문제이기 때문이다. 집중을 빡하고 풀어줘야지 실력도 상승하고 긴장감도 유지할 수 있기 때문이다. 그렇기에 해당 문제를 해결해보았다.

문제 유형은 정말 4~5가지의 알고리즘 문제를 한문제에 합쳐둔것 처럼 어렵다. 하지만 차근차근 문제를 풀어나가다 보면 어느순간 해결이 되어있는데 그 짜릿함이 있다. 그래서 오랜만에 구현문제들로 풀어보았다. 요즘은 다른 유형이 생겨났던데 굉장히 어려워서 그거는 풀지 않았다.

해당 문제는 범위를 늘려줘야하는 것에 키포인트가 있다고 생각한다. 골렘이 초반에 필요한 열은 3칸이다. 행은 뭐 신경쓸 필요는 없고, 그래서 열의 길이보다 +3칸 늘려줘서 거기서부터 시작하는 것이 좋다.
그래야 생각도 편해진다. 대신 열의 길이를 늘렸기 때문에 score는 -3을 해줘야한다. 당연한것이다. 그리고 항상 isRange()라는 함수를 많이 사용하게 되는데, 그 범위 또한 늘려주고 줄여주고 해줘야한다. 당연하다.

 

그리고 해당 문제를 해결할때는 글을 잘 못 읽어서 문제가 되었다. 나는 자리를 찾으려고 좌측으로 끝까지 이동해서 내려갈 곳을 찾고 없다면 우측으로 끝까지 이동해서 찾는 것인줄 알았는데... 그게 아니더라!

단 한번 이동해보고 판단하는 것이더라 항상 글이 긴 내용을 정리를 잘하고 설계를 잘하고 들어가야하는데 매번 오만함이 나를 힘들게 한다. 항상 겸손하자! 아래는 정답 코드이다 주석을 달아놨으니 보기 나쁘지만 이런 문제를 풀때는 주석이 필수이다.

#include<iostream>
#include<queue>
#include<cstring>

#define MAX 77
using namespace std;

struct Golem{
	
	int centerY;
	int centerX;
	int exits;
	
};

Golem golems[1010];
int answer,n,m,k,x,exits,board[MAX][MAX],visited[MAX][MAX],exitCheck[MAX][MAX];

const int dy[4] = {-1,0,1,0};
const int dx[4] = {0,1,0,-1}; // n, e, s, w

int checkMove[3] = {2,3,1};
int checkDir[4][4] = {{},{0,1,2},{1,2,3},{2,3,0}};

void init(){
	
	/*
		골렘은 5개 모양으로 이루어져 있고, 그 위치가 마이너스에
		위치해 있으면 안된다.
		
		그렇기 때문에 골렘이 있을 수 있는 위 세칸을 비워두는데
		이 부분을 1 ~ 3 까지로 정한다. 
		
		그래서 초기 골렘의 위치는 2가 된다. 
	*/ 
	
	answer = 0;
	cin >> n >> m >> k;
	
	// 골렘의 숫자도, 1부터 k까지 이루어진다. 0이 된다면 끔찍! 
	for(int i = 1; i <= k; i++){
		cin >> x >> exits;
		golems[i] = {2,x,exits};
	}
	
}

int isRange(int y, int x){
	// 그래서 골렘의 범위는 1부터 n + 3까지로 정했다.
	// 왜냐하면 처음 내려오면 골렘의 중요부위는 4에 위치하게 되고
	// 머리 부분은 2에 위치하게되는데, 그렇게되면 range범위에서 걸린다.
	// 일단 위 3칸도 현재는 나의 편이라고 생각하자. 
	return 1 <= y && y <= n+3 && 1 <= x && x <= m;
}

int isNotRange(int y, int x){
	// 이제 여기부터는 내 범위가 아니다.
	// 1 ~ 3은 나의 범위가 아님을 이해하자. 
	return 1 <= y && y <= 3 && 1 <= x && x <= m;
} 

int moveDown(int y, int x, int golemNumber) {
	
	// moveDown 부분이다. 방향은 아래를 가리키고 있다.
	// 이전에 나는 mock up table을 만들어서 3방향이 비워져있는지 파악했다. 
	int dir = 2;
	int check = true;
	
	int ny = y + dy[dir];
	int nx = x + dx[dir]; // 현재 아래로 내려갈 것이기때문에, 아래에서 3방향을 살펴보자. 
	
	for(int i = 0; i < 3; i++){
		// 3방향을 살펴보자. checDir[down][0~2]; 
		int nny = ny + dy[checkDir[dir][i]];
		int nnx = nx + dx[checkDir[dir][i]];
		
		// board에 있는지 없는지 있다면 이동하지 못한다. 
		if(board[nny][nnx]) check = false; // 하나라도 false면 이동 불가 
		// 범위 내로 있는지 알아야한다. 끝까지 내려갈 순 없다.
		// 물론 for문n이 막아줄것이다. 하지만 좌우도 있지 않겠는가? 
		if(!isRange(nny,nnx)) check = false;	
	}
	
	// 아직은 업데이틀 해줄 필요가 없음. 
	// 출구도 바뀌지 않고, 배열에서만 업데이트를 해주면 됨.
	// 만약 내려가는 것이 통과가 됐다면? 현재 정령의 위치를 바꿔주게 된다. 
	if(check){
		golems[golemNumber].centerX = nx;
		golems[golemNumber].centerY = ny;
	}
	
	return check; 
}
// 서쪽으로 이동했지만, 아래로 내려가지 못했다면? 
int moveLeft(int y, int x, int golemNumber) {
	
	int dir = 3;
	int check = true;
	
	int ny = y + dy[dir];
	int nx = x + dx[dir];
	
	for(int i = 0; i < 3; i++){
		int nny = ny + dy[checkDir[dir][i]];
		int nnx = nx + dx[checkDir[dir][i]];
		
		if(board[nny][nnx]) check = false; // 하나라도 false면 이동 불가 
		if(!isRange(nny,nnx)) check = false;	
	}
	
	// 아직은 업데이틀 해줄 필요가 없음. 
	// 출구도 바뀌지 않고, 배열에서만 업데이트를 해주면 됨.
	
	if(check){
		golems[golemNumber].centerX = nx;
		golems[golemNumber].centerY = ny;
	}
	
	return check; 
}

int moveRight(int y, int x, int golemNumber){
	
	int dir = 1;
	int check = true;
	
	int ny = y + dy[dir];
	int nx = x + dx[dir];
	
	for(int i = 0; i < 3; i++){
		int nny = ny + dy[checkDir[dir][i]];
		int nnx = nx + dx[checkDir[dir][i]];
		
		if(board[nny][nnx]) check = false; // 하나라도 false면 이동 불가 
		if(!isRange(nny,nnx)) check = false;	
	}
	
	// 아직은 업데이틀 해줄 필요가 없음. 
	// 출구도 바뀌지 않고, 배열에서만 업데이트를 해주면 됨.
	
	if(check){
		golems[golemNumber].centerX = nx;
		golems[golemNumber].centerY = ny;
	}
	
	return check; 
}

struct INFO{
	
	int y;
	int x;
	int number;
	
};

void bfsGolem(int y, int x, int golemNumber){
	//우리에게 필요한건, 골렘의 위치, 그리고 골렘의 번호이다. 
	memset(visited,0,sizeof(visited));
	queue<INFO> q;
	q.push({y,x,golemNumber});
	visited[y][x] = 1;
	
	int downest = -1; // 제일 낮은곳을 -1로 설정해보자. 
	
	while(!q.empty()){
		
		int y = q.front().y;
		int x = q.front().x;
		int number = q.front().number;
		q.pop();
		
		// 매번 위치를 파악해보자. 위치가 다른 방식으로 낮을 수 있다. 
		if(downest <= y){
			downest = y;
		}
		
		for(int i = 0; i < 4; i++){
			int ny = y + dy[i];
			int nx = x + dx[i];
			
			// 방향이동했다면? 
			
			// 방문했다면? 그대로 
			if(visited[ny][nx]) continue;
			// 범위를 벗어났다면? 그대로 진행, 1,2,3 내부엔 없다 어차피 
			if(!isRange(ny,nx)) continue;
			// 골렘이 없는 곳이라면? 그대로 진행 
			if(!board[ny][nx]) continue;
			// board[ny][nx]가 0초과이고, 그 전자리가 탈출자리라면?
			// 이동이 가능핟. 이때만 이동이 가능하다. 
			if(board[ny][nx] != number && exitCheck[y][x]) {
				visited[ny][nx] = 1;
				// 그리고 현재 번호를 변경해주자. 
				q.push({ny,nx,board[ny][nx]});
			}
			// 인접한 녀석이 나랑 번호가 같다면? 
			// 방문처리를 해주자. 
			else if(board[ny][nx] == number) {
				visited[ny][nx] = 1;
				q.push({ny,nx,number});
			}
		}
		
	}
	
//	cout << "move golem!" << golemNumber << "\n";
//	
//	for(int i = 1; i <= n + 3; i++){
//		for(int j = 1; j <= m; j++){
//			cout << visited[i][j] << " ";
//		}
//		cout << "\n";
//	}
//	
//	
//	cout <<"down ! : "<< downest << "\n";
	
	// 현재 위치는 +3을 해주었기 떄문에 -3을 해줘야한다. 
	answer += (downest - 3);
	
}

void clearBoard() {
	
	memset(board,0,sizeof(board));
	memset(exitCheck,0,sizeof(exitCheck));
	
}

void moveGolem(Golem g, int golemNumber) {
	
	// 골렘이 이동을 시작한다.
	// 골렘의 현재 상태가 주어진다.
	// y,x,exit,golemNumber이렇게 4개가 주어진다. 
	
	// 매번 이동상황마다 현 상태가 바뀌기 때문에, 이렇게 처리해야한다. 
	for(int i = 0; i < n; i++){
		
		int y = golems[golemNumber].centerY;
		int x = golems[golemNumber].centerX;
		int exits = golems[golemNumber].exits;
		
		// 현재 우선순위를 나타내주고있다. 
		
		// 일단 한칸 내려보는 것부터 시작을 하자. 
		if(moveDown(y,x,golemNumber)) continue;
		// left 이동도 같은 부분이다. 좌측으로 이동해서 3방향을 살펴보자. 
		if(moveLeft(y,x,golemNumber)){
			// 좌측 이동이 되었다면? 정령의 위치를 바꾸어줬기 떄문에, 정령의 위치를 매개변수로 다시 넣는다. 
			if(moveDown(golems[golemNumber].centerY, golems[golemNumber].centerX,golemNumber)) {
				// 방향전환
				// 방향전환해서 다운까지 성공했다면? 출구가 바뀌게 된다. 
				// 이동후 정령의 위치를 바꿔주기 때문에, 정령은 바꾸지 않아도 된다. 
				exits -= 1;
				if(exits < 0) exits = 3; 
				golems[golemNumber].exits = exits;
				continue;
			}
			else{
				// 내려가지 못했다면?  정령의 위치를 원상복귀한다. 
				// 정령의 위치를 재배치해주고 
				golems[golemNumber].centerY = y;
				golems[golemNumber].centerX = x;
			} 
		}
		// 우측 이동도 생각해보자. 
		if(moveRight(y,x,golemNumber)) {
			if(moveDown(golems[golemNumber].centerY, golems[golemNumber].centerX,golemNumber)) {
				// 방향전환 
				exits += 1;
				if(exits == 4) exits = 0;
				golems[golemNumber].exits = exits;
				continue;
			}
			else {
				golems[golemNumber].centerY = y;
				golems[golemNumber].centerX = x;
			}
		}
		else break;
		
	}
	
	// 위치 변화된곳 update 해주고, 
	// 해당 위치에서 4방향중에 범위를 벗어난 녀석이 있는지 파악하자. 
	int chk = 0;
	int y = golems[golemNumber].centerY;
	int x = golems[golemNumber].centerX;
	int exits = golems[golemNumber].exits;
	
	int ey = y + dy[exits];
	int ex = x + dx[exits];
	
	exitCheck[ey][ex] = 1;
	
	board[y][x] = golemNumber;
	
	// 골렘의 위치, 출구 방향, 출구 위치 전부 주어졌다. 
	
	
	// board 업데이트 해주기 
	// 4방향을 모두 체크하고, 만약 단 하나라도 범위를 벗어났다면? 
	for(int i = 0; i < 4; i++){
		int ny = y + dy[i];
		int nx = x + dx[i];
		board[ny][nx] = golemNumber;
		
		if(isNotRange(ny,nx)) chk = 1;
	}
	
	if(chk) {
		
		// 숲속에 있는 골렘들 전부 빠져나가기. 
		// 숲속 골렘들을 전부 제거해주는 역할을 한다.
		// 물론 현재 정령은 더하지 않는다. 
		clearBoard();
		//clearExit();
		
	} else{
		
		// 최대한 남쪽으로 내려가는 골렘들 생각하기. 
		// bfs 돌리기. 현재 위치(서있는 골렘)가 출구라면?
		// 그리고 이제 최대한 내려가보자. 
		bfsGolem(y,x,golemNumber);
	}
	
}

void printBoard() {
	
	cout << "print board!" << "\n";
	for(int i = 1; i <= n+3; i++){
		for(int j = 1; j<= m; j++){
			cout << board[i][j] << " ";
		}
		cout << "\n";
	}
	cout << "\n";
	
	cout << "print exit board!" << "\n";
	for(int i = 1; i <= n+3; i++){
		for(int j = 1; j<= m; j++){
			cout << exitCheck[i][j] << " ";
		}
		cout << "\n";
	}
	cout << "\n";
	
	for(int i = 1; i <= k; i++){
		cout << "number : " << i << "\n";
		cout << "golems exits : "<< golems[i].exits << "\n";
	}
	
}

void solve() {
	
	for(int i = 1; i <= k; i++){
		moveGolem(golems[i],i);
		//printBoard();
	}
	
}

int main() {
	
	init();
	solve();
	cout << answer;
	
	return 0;
}

'PS > CodeTree' 카테고리의 다른 글

[기출문제] 고대 문명 유적 탐사  (0) 2024.09.29