알고리즘/백준

백준 3190번 문제 뱀 C/C++ 풀이

Chavo Kim 2020. 8. 4. 14:46

시뮬레이션류 문제는 푸는게 머리 아프다.

문제에 있는 내용들을 그대로 코드로 구현해야하는데,

어떤 자료구조가 좋을지 결정해야한다.

 

사실 아는거라고 해봤자 배열밖에 없었는데,

이번 문제는 Deque(덱)을 이용하면 쉽게 풀 수 있는 문제였다.

(배열을 이용한 괴상한 풀이도 적어놓을 예정,..,,)

 

덱을 알기 위해서는 큐를 먼저 알아야 한다.

 

Queue(큐)란?

 

먼저 넣은 데이터가 먼저 나오는 선입선출(First-In, First-Out, FIFO) 구조이다. 후입선출(Last-In, First-Out, LIFO)를 가진 stack과는 반대되는 개념이다. 줄 서서 먹는 식당으로 생각하면 될 것 같다. 줄을 기다릴 때 온 순서대로 사람들이 차곡차곡 쌓이고 맨 먼저 온 사람부터 식당에 들어가니까. 시간 순서대로 사람들이 오는 것, 즉 자료를 넣는 것을 'push'라고 하고 먼저 온 사람이 먹으러 식당으로 들어가는 것, 즉 자료가 나가는 것을 'pop'이라고 한다.

 

그러면 Deque(덱)은?

 

Double-ended Queue의 약자로 양쪽 끝에서 모두 삽입과 삭제가 가능한 자료구조를 뜻하게 된다. 뱀 문제의 경우 머리와 꼬리가 삭제될 때도 있고 새로 추가될 때도 있는데 덱을 사용하면 이를 관리하기가 용이하다.

 

 

해당 문제를 배열로 푼다면..?

 

#include <stdio.h>

//input이 긴 문제는 일일히 input 치기가 귀찮아서 fopen으로 input을 넣는다.
FILE* in = fopen("input.txt", "r");

int n;
int k;
int l;
int input_array[101][101];
char input_dir[10001];
int dir_x[4] = { 0, 1, 0, -1 };
int dir_y[4] = { 1, 0, -1, 0 };
int  head_dir_x, head_dir_y, tail_dir_x, tail_dir_y, head_x, head_y, tail_x, tail_y;
int time_delay;
bool possible = true;
int time;
int tmp_dir_x, tmp_dir_y;

void turn(int time) {
	//다음 head의 방향을 판단
	if (input_dir[time] == 'D')
	{
		head_dir_x = (head_dir_x + 1) % 4;
		head_dir_y = (head_dir_y + 1) % 4;
	}
	else if (input_dir[time] == 'L')
	{
		head_dir_x = (head_dir_x + 3) % 4;
		head_dir_y = (head_dir_y + 3) % 4;
	}

	//head 위치 갱신
	head_x = head_x + dir_x[head_dir_x];
	head_y = head_y + dir_y[head_dir_y];

	//tail의 경우 사과를 먹으면 방향이 바뀌지 않을 수 있기 때문에 tmp로 tail의 방향을 저장해둔다.
	tmp_dir_x = tail_dir_x;
	tmp_dir_y = tail_dir_y;

	//다음 tail의 방향을 판단
	if (input_dir[time - time_delay] == 'D')
	{
		tail_dir_x = (tail_dir_x + 1) % 4;
		tail_dir_y = (tail_dir_y + 1) % 4;
	}
	else if (input_dir[time - time_delay] == 'L')
	{
		tail_dir_x = (tail_dir_x + 3) % 4;
		tail_dir_y = (tail_dir_y + 3) % 4;
	}

	//다음 head의 위치에 따른 움직임
    //head 위치에 아무것도 없다면,
	if (input_array[head_x][head_y] == 0)
	{
    	//head에 뱀이 있다는 것을 표시
		input_array[head_x][head_y] = 1;
        //기존 tail은 위치 삭제
		input_array[tail_x][tail_y] = 0;
        //tail 위치 갱신
		tail_x = tail_x + dir_x[tail_dir_x];
		tail_y = tail_y + dir_y[tail_dir_y];
	}
    //head 위치에 사과가 있다면
	else if (input_array[head_x][head_y] == 2)
	{
    	//head에 뱀이 있다는 것을 표시
		input_array[head_x][head_y] = 1;
        //tail은 그대로 남고
		input_array[tail_x][tail_y] = 1;
        //head와 tail의 방향 전환이 시간 간격이 1만큼 더 차이나게 됨
		time_delay++;
        //원래의 tail 방향대로 돌려놓기
		tail_dir_x = tmp_dir_x;
		tail_dir_y = tmp_dir_y;
	}
    //head 위치에 뱀의 몸이 있다면
	else if (input_array[head_x][head_y] == 1)
	{
    	//fail
		possible = false;
	}
}

int main() {
	//입력
	fscanf(in, "%d", &n);
	fscanf(in, "%d", &k);
	for (int i = 0; i < k; i++)
	{
		int tmp_x, tmp_y;
		fscanf(in, "%d %d", &tmp_x, &tmp_y);
		input_array[tmp_x - 1][tmp_y - 1] = 2;
	}
	fscanf(in, "%d", &l);
	for (int i = 0; i < l; i++)
	{
		int tmp_s;
		char tmp_dir;
		fscanf(in, "%d %c", &tmp_s, &tmp_dir);
		input_dir[tmp_s] = tmp_dir;
	}
    //불가능할 때까지 반복
	while (1==1)
	{
		turn(time);
        time++;
        //불가능 조건, head가 벽을 벗어나거나 뱀의 몸과 부딪히거나
		if (head_x < 0 || head_x >= n || head_y < 0 || head_y >= n || !possible)
			break;
        //디버깅용 출력
		/*for (int i = 0; i < n; i++)
		{
			for (int j = 0; j < n; j++)
				printf("%d ", input_array[i][j]);
			printf("\n");
		}
		printf("%d\n\n\n", time);*/
	}
    //답 출력
	printf("%d", time);
}

 

이와 같은 더럽고 무지막지한 코드를 얻게 된다...

 

하지만 deque로 풀어본다면?

 

 

#include <cstdio>
#include <utility>
#include <deque>
#include <queue>

using namespace std;

//input이 긴 문제는 일일히 input 치기가 귀찮아서 fopen으로 input을 넣는다.
FILE* in = fopen("input.txt", "r");

int n;
int k;
int l;
//자료구조 특성을 보이고자 시간에 따른 방향 값을 나타내는 dir은 queue로 snake는 deque으로 표시
deque<pair<int, int>> snake;
queue<pair<int, char>> dir;
int input_array[101][101];
int dir_x[4] = { 0, 1, 0, -1 };
int dir_y[4] = { 1, 0, -1, 0 };
int dir_num;
int time = 1;

// 뱀의 다음 방향을 결정
void direction(queue<pair<int, char>>& dir, int time) {
	if (dir.front().first == time - 1)
	{
		if (dir.front().second == 'D')
			dir_num = (dir_num + 1) % 4;
		else if (dir.front().second == 'L')
			dir_num = (dir_num + 3) % 4;
		dir.pop();
	}
}

bool snake_go(deque<pair<int, int>>& snake, queue<pair<int, char>>& dir, int time) {
	//dir queue가 비어있다면 dir.front()에서 오류가 나기 때문에 비어있는지 확인 후 진행
	if(!dir.empty())
		direction(dir, time);
	int tmp_x = snake.back().first + dir_x[dir_num];
	int tmp_y = snake.back().second + dir_y[dir_num];
    //뱀이 벽 밖으로 나간 경우
	if (tmp_x < 0 || tmp_x >= n || tmp_y < 0 || tmp_y >= n)
		return false;
    //뱀이 자신의 몸이랑 부딪히는 경우, deque은 순회 가능!
	for (int i = 0; i < snake.size(); i++)
	{
		if (snake[i].first == tmp_x && snake[i].second == tmp_y)
			return false;
	}
    //모두 해당 안된다면 새로운 뱀 위치 추가
	snake.push_back(make_pair(tmp_x, tmp_y));
    //가려는 위치가 비어 있다면 뱀의 가장 오래된 위치를 삭제
	if (input_array[tmp_x][tmp_y] == 0)
	{
		snake.pop_front();
	}
    //아니라면 사과 먹었으니 빈 곳으로 갱신
	input_array[tmp_x][tmp_y] = 0;
    //다음 시간대로 진행
	return true;
}

int main() {
	//입력
	fscanf(in, "%d", &n);
	fscanf(in, "%d", &k);
	for (int i = 0; i < k; i++)
	{
		int tmp_x, tmp_y;
		fscanf(in, "%d %d", &tmp_x, &tmp_y);
		input_array[tmp_x - 1][tmp_y - 1] = 2;
	}
	fscanf(in, "%d", &l);
	for (int i = 0; i < l; i++)
	{
		int tmp_s;
		char tmp_dir;
		fscanf(in, "%d %c", &tmp_s, &tmp_dir);
		dir.push(make_pair(tmp_s, tmp_dir));
	}
	snake.push_back(make_pair(0, 0));
	//불가능할 때까지 반복
	while (snake_go(snake, dir, time))
	{
		time++;
	}
	printf("%d", time);
}

깔-끔