본문 바로가기
PS 문제 풀이/Baekjoon

[BOJ] 백준 23290 마법사 상어와 복제

by 까망 하르방 2021. 11. 27.
반응형

Approach

출처https://www.acmicpc.net/problem/23290

 

실제 시험장에서 문제 설명과 Test Case는 알 수 없지만,,,

문제 조건이 갈수록 이해하기 어려워지는 경향이 있는 것 같네요 😒

 

이번 문제는 시뮬레이션 구현 문제로

주어진 Test Case를 하나하나 따라가다 보면 구현할 수 있는데

문제 조건과 함께 DFS와 BFS 구현을 해야 했던 것 같습니다.

void process()
{
        while (S--)
        {
               copyMagic(SAVE);
               moveFish();
               removeSmell();
               moveShark();
               copyMagic(LOAD);
        }
}

① 물고기 저장 (복제 마법)

② 물고기 이동

③ (기존) 물고기 냄새 처리

④ 상어 이동

⑤ 저장한 물고기 반영

복제 마법

inline void copyMagic(int mode)
{
        switch (mode)
        {
        case SAVE: // 물고기 저장
               _memcpy(copyMap, map);
               break;

        case LOAD: // 저장해둔 물고기를 좌표에 push
               for (int i = 1; i <= N; ++i)
               {
                       for (int j = 1; j <= N; ++j)
                       {
                              for (int k = 0; k < copyMap[i][j].size(); k++)
                              {
                                      
                                      map[i][j].push_back(copyMap[i][j][k]);
                              }
                       }
               }
               break;
        }
}

물고기를 저장하는 것과 반영하는 것을 하나의 함수에서 처리했습니다.

실제 문제 풀 때는 두 개의 함수로 처리하는 것이 좋겠죠..?

물고기가 이동하기전에 copyMap[][]에 복사해두고

Process Cycle 한 번 끝날 때 저장해둔 물고기를 map[][]에 반영

 

* map[][]은 물고기 객체를 저장할 수 있도록 vector 활용

vector<Node> map[MAX_N][MAX_N];

 

물고기 이동

Node move(Node cur)
{
        int nextX = cur.x;
        int nextY = cur.y;
        
        for (int d = 0; d < 8; d++)
        {
               nextX = cur.x + fdx[cur.dir];
               nextY = cur.y + fdy[cur.dir];
               if (nextX >= 1 && nextX <= N && nextY >= 1 && nextY <= N)
               {
                       if (!(nextX == shark.x && nextY == shark.y)
                              && fishSmellMap[nextX][nextY] == 0)
                       {
                              // 물고기가 이동가능한 경우
                              return { nextX, nextY, cur.dir };
                       }
               }
               // 방향 전환
               cur.dir = rotateDir(cur.dir);
        }
        // 이동하지 못하는 경우
        return cur;
}

void moveFish()
{
        vector<Node> TMap[MAX_N][MAX_N];
        for (int i = 1; i <= N; i++)
        {
               for (int j = 1; j <= N; j++)
               {
                       for (int k = 0; k < map[i][j].size(); k++)
                       {
                              Node f = move(map[i][j][k]);
                              TMap[f.x][f.y].push_back({ f.x, f.y, f.dir });
                       }
               }
        }
        _memcpy(map, TMap);
}

map[][]에 있는 물고기들을 전체적으로 움직여 줍니다.

움직인 물고기를 다시 움직이지 않도록

초기 map[][]과 동일한 TMap[][] 만들어서

그 곳에 한마리씩 옮겨준 후 모두 끝나고 다시 map[][]에 옮겨주었습니다.

 

물고기가 이동할 때는 8개 방향을 살피는데

아래 조건들을 확인해야 합니다.

① 범위를 벗어나는 방향인지

② 상어가 있는 곳인지

③ 물고기 냄새가 남아 있는 곳인지

 

* 물고기 방향은 반시계 45도 방향 회전으로 아래와 같이 구현

int rotateDir(int d) { return (d - 1) == 0 ? 8 : (d -1); }

 

상어 이동

int huntFish()
{
        Node cur = { shark.x, shark.y };
        int ret = 0;
        bool visit[MAX_N][MAX_N] = { false, };
        for (int i = 0; i < 3; i++)
        {
               int dir = trr[i];
               int nextX = cur.x + sdx[dir];
               int nextY = cur.y + sdy[dir];
               // 경로를 벗어나는 경우
               if (nextX < 1 || nextX > N || nextY < 1 || nextY > N)
                       return -1;
               // 재방문 하지 않은 곳을 물고기 개수 더해주기
               if (!visit[nextX][nextY])
               {
                       visit[nextX][nextY] = true;
                       ret += map[nextX][nextY].size();
               }
               cur = { nextX, nextY };
        }
        return ret;
}

void DFS(int idx)
{
        if (idx == 3)
        {
               int val = huntFish();
               if (maxValue < val)
               {
                       // 경로 저장
                       for (int i = 0; i < 3; i++)
                       {
                              arr[i] = trr[i];
                       }
                       maxValue = val;
               }
               return;
        }
        for (int i = 1; i <= 4; ++i)
        {
               trr[idx] = i;
               DFS(idx + 1);
        }
}

void moveShark()
{
        maxValue = -1;
        DFS(0);
        for (int i = 0; i < 3; i++)
        {
               int nextX = shark.x + sdx[arr[i]];
               int nextY = shark.y + sdy[arr[i]];
               // 물고기가 존재한다면
               if (map[nextX][nextY].size() != 0)
               {
                       fishSmellMap[nextX][nextY] = 2;
                       map[nextX][nextY].clear();
               }
               shark = { nextX, nextY };
        }
}

 

상어 이동방향은 물고기를 가장 많이 먹을 수 있는 경로로 하되

동일한 경우 「사전순 우선순위」를 가집니다.

[상, 상, 상] → [상, 상, 좌]   → [우, 우, 하] → [우, 우, 우]

 

이는 [1, 1, 1] → [1, 1, 2]   → [4, 4, 3] → [4, 4, 4] 인데,

중복 순열/조합을 통해서 구현할 수 있다.

▶ 순열과 조합

 

순열과 조합 (백준 N과 M 시리즈)

순열과 조합 순열(Permutation) / 조합(Combination)에서 개수를 구하는 경우에는 ▶ P(n, r) = n × (n - 1) × (n - 2) × ... × (n - r - 1) = n! / (n - r)! 상황에 따라서는 주어지는 Data가 나올..

zoosso.tistory.com

문제에서 제시한 순서로 방향을 임시로 정해서

해당 [방향 구성]으로 물고기를 많이 먹는대로 방향을 갱신해준다.

 

익숙하지 않다면 실제 시험장에서는 DFS를 사용하지 않고 

64 가지 경우를 하드 코딩(LUT)하는 것도 해결책이 될 수 있습니다.

▶ 시간 성능 향상을 위한 코드 최적화 (C/C++)

 

[알고리즘] 시간 성능 향상을 위한 코드 최적화 (C/C++)

register 변수 사용 변수를 선언할 때 앞에 register 라는 키워드를 붙이면 변수는 RAM 대신 CPU의 레지스터를 사용한다. 따라서 변수에 접근하는 속도가 다른 일반적인 변수보다 빨라진다. 단, 레지스

zoosso.tistory.com

방향이 정해진 다음에는 이동하면서 물고기를 먹고

물고기 냄새를 남깁니다.

 

물고기 냄새 처리

void removeSmell()
{
        for (int i = 1; i <= N; i++)
        {
               for (int j = 1; j <= N; j++)
               {
                       if (fishSmellMap[i][j] > 0)
                              fishSmellMap[i][j]--;
               }
        }
}

물고기 냄새는 전체 map[][]에서 남아있는 냄새 감소

* 「물고기 냄새」가 있는 곳으로 이동은 불가하지만

    복제 마법으로 그 곳에 생성될 수는 있다 


#include <iostream>
#include <vector>

using namespace std;

struct Node
{
	int x, y, dir;
} shark;

enum Magic
{
	SAVE,
	LOAD
};

inline int rotateDir(int d) { return (d - 1) == 0 ? 8 : (d - 1); }
const int MAX_N = 5;
const int N = 4;
const int sdx[] = { 0, -1, 0, 1, 0 };
const int sdy[] = { 0, 0, -1, 0, 1 };
const int fdx[] = { 0, 0, -1, -1, -1, 0, 1, 1, 1 };
const int fdy[] = { 0, -1, -1, 0, 1, 1, 1, 0, -1 };

int M, S, maxValue, ans;
int arr[3], trr[3];
int fishSmellMap[MAX_N][MAX_N];
vector<Node> map[MAX_N][MAX_N], copyMap[MAX_N][MAX_N];

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

	int x, y, d;
	for (int i = 0; i < M; i++)
	{
		scanf("%d %d %d", &x, &y, &d);
		Node f = { x, y, d };
		map[x][y].push_back(f);
	}

	scanf("%d %d", &shark.x, &shark.y);
}

inline void _memcpy(vector<Node> to[][MAX_N], vector<Node> from[][MAX_N])
{
	for (int i = 1; i <= N; i++)
	{
		for (int j = 1; j <= N; j++)
		{
			to[i][j] = from[i][j];
		}
	}
}

inline void copyMagic(int mode)
{
	switch (mode)
	{
	case SAVE: // 물고기 저장
		_memcpy(copyMap, map);
		break;

	case LOAD: // 저장해둔 물고기를 좌표에 push
		for (int i = 1; i <= N; ++i)
		{
			for (int j = 1; j <= N; ++j)
			{
				for (int k = 0; k < copyMap[i][j].size(); k++)
				{

					map[i][j].push_back(copyMap[i][j][k]);
				}
			}
		}
		break;
	}
}

Node move(Node cur)
{
	int nextX = cur.x;
	int nextY = cur.y;

	for (int d = 0; d < 8; d++)
	{
		nextX = cur.x + fdx[cur.dir];
		nextY = cur.y + fdy[cur.dir];

		if (nextX >= 1 && nextX <= N && nextY >= 1 && nextY <= N)
		{
			if (!(nextX == shark.x && nextY == shark.y)
				&& fishSmellMap[nextX][nextY] == 0)
			{
				// 물고기가 이동가능한 경우
				return { nextX, nextY, cur.dir };
			}
		}

		// 방향 전환
		cur.dir = rotateDir(cur.dir);
	}

	// 이동하지 못하는 경우
	return cur;
}

void moveFish()
{
	vector<Node> TMap[MAX_N][MAX_N];

	for (int i = 1; i <= N; i++)
	{
		for (int j = 1; j <= N; j++)
		{
			for (int k = 0; k < map[i][j].size(); k++)
			{
				Node f = move(map[i][j][k]);
				TMap[f.x][f.y].push_back({ f.x, f.y, f.dir });
			}
		}
	}

	_memcpy(map, TMap);
}

int huntFish()
{
	Node cur = { shark.x, shark.y };
	int ret = 0;
	bool visit[MAX_N][MAX_N] = { false, };

	for (int i = 0; i < 3; i++)
	{
		int dir = trr[i];
		int nextX = cur.x + sdx[dir];
		int nextY = cur.y + sdy[dir];

		// 경로를 벗어나는 경우
		if (nextX < 1 || nextX > N || nextY < 1 || nextY > N)
			return -1;

		// 재방문 하지 않은 곳을 물고기 개수 더해주기
		if (!visit[nextX][nextY])
		{
			visit[nextX][nextY] = true;
			ret += map[nextX][nextY].size();
		}
		cur = { nextX, nextY };

	}
	return ret;
}

void DFS(int idx)
{
	if (idx == 3)
	{
		int val = huntFish();
		if (maxValue < val)
		{
			// 경로 저장
			for (int i = 0; i < 3; i++)
			{
				arr[i] = trr[i];
			}
			maxValue = val;
		}
		return;
	}

	for (int i = 1; i <= 4; ++i)
	{
		trr[idx] = i;
		DFS(idx + 1);
	}
}

void moveShark()
{
	maxValue = -1;
	DFS(0);

	for (int i = 0; i < 3; i++)
	{
		int nextX = shark.x + sdx[arr[i]];
		int nextY = shark.y + sdy[arr[i]];

		// 물고기가 존재한다면
		if (map[nextX][nextY].size() != 0)
		{
			fishSmellMap[nextX][nextY] = 2;
			map[nextX][nextY].clear();
		}

		shark = { nextX, nextY };
	}
}

void removeSmell()
{
	for (int i = 1; i <= N; i++)
	{
		for (int j = 1; j <= N; j++)
		{
			if (fishSmellMap[i][j] > 0)
				fishSmellMap[i][j]--;
		}
	}
}

void process()
{
	while (S--)
	{
		copyMagic(SAVE);
		moveFish();
		removeSmell();
		moveShark();
		copyMagic(LOAD);
	}
}

void output()
{
	// 남아 있는 물고기
	for (int i = 1; i <= N; i++)
	{
		for (int j = 1; j <= N; j++)
		{
			ans += map[i][j].size();
		}
	}
	printf("%d\n", ans);
}

int main()
{
	// freopen("Input.txt", "r", stdin);
	input();
	process();
	output();
}

 

📌 함께 보면 좋은 포스팅

 N과 M (3)

• 시간 성능 향상을 위한 코드 최적화 (C/C++)

순열과 조합

• 삼성 SW 기출 모음

 삼성 SW 코딩 테스트 준비(A형)

반응형

'PS 문제 풀이 > Baekjoon' 카테고리의 다른 글

[BOJ] 백준 23291 어항 정리  (3) 2021.12.11
[BOJ] 백준 23289 온풍기 안녕!  (0) 2021.12.07
[BOJ] 백준 23288 주사위 굴리기2  (0) 2021.11.21
[BOJ] 백준 1966 프린터큐  (0) 2021.09.20
[BOJ] 백준 1057 토너먼트  (0) 2021.09.18

댓글