-
JUNGOL/Intermediate_Coder/그래프탐색-DFS/1840 : 치즈코딩 테스트/JUNGOL 2021. 12. 28. 15:32
Intermediate_Coder/그래프탐색-DFS/치즈
문제
아래 <그림 1>과 같이 정사각형 칸들로 이루어진 사각형 모양의 판이 있고,
그 위에 얇은 치즈(회색으로 표시된 부분)가 놓여 있다.
판의 가장자리(<그림 1>에서 네모칸에 엑스친 부분)에는 치즈가 놓여 있지 않으며 치즈에는 하나 이상의 구멍이 있을 수 있다.
이 치즈를 공기 중에 놓으면 녹게 되는데 공기와 접촉된 칸은 한 시간이 지나면 녹아 없어진다.
치즈의 구멍 속에는 공기가 없지만 구멍을 둘러싼 치즈가 녹아서 구멍이 열리면 구멍 속으로 공기가 들어 가게 된다.
<그림 1>의 경우, 치즈의 구멍을 둘러싼 치즈는 녹지 않고 ‘c’로 표시된 부분만 한 시간 후 에 녹아 없어져서 <그림 2>와 같이 된다.
다시 한 시간 후에는 <그림 2>에서 ‘c’로 표시된 부분이 녹아 없어져서 <그림 3>과 같이 된다.
<그림 3>은 원래 치즈의 두 시간 후 모양을 나타내고 있으며, 남은 조각들은 한 시간이 더 지나면 모 두 녹아 없어진다.
그러므로 처음 치즈가 모두 녹아 없어지는 데는 세 시간이 걸린다.
<그림 3>과 같이 치즈가 녹는 과정에서 여러 조각으로 나누어 질 수도 있다.
입력으로 사각형 모양의 판의 크기와 한 조각의 치즈가 판 위에 주어졌을 때,
공기 중에서 치즈가 모 두 녹아 없어지는 데 걸리는 시간과
모두 녹기 한 시간 전에 남아있는 치즈 조각이 놓여 있는 칸의 개수를 구하는 프로그램을 작성하시오.입력 형식
입력 파일의 첫째 줄에는 사각형 모양 판의 세로와 가로의 길이가 양의 정수로 주어진다.
세로와 가로의 길이는 최대 100이다. 판의 각 가로 줄의 모양이 윗 줄부터 차례로 입력 파일의 둘째 줄부터 마지막 줄까지 주어진다.
치즈가 없는 칸은 0, 치즈가 있는 칸은 1로 주어 지며 각 숫자 사이에는 빈칸이 하나씩 있다.
출력 형식
첫째 줄에는 치즈가 모두 녹아서 없어지는 데 걸리는 시간을 출력하고, 둘째 줄에는 모두 녹기 한 시간 전에 남아있는 치즈 조각이 놓여 있는 칸의 개수를 출력한다.
입력 예
13 12
0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 1 1 0 0 0
0 1 1 1 0 0 0 1 1 0 0 0
0 1 1 1 1 1 1 0 0 0 0 0
0 1 1 1 1 1 0 1 1 0 0 0
0 1 1 1 1 0 0 1 1 0 0 0
0 0 1 1 0 0 0 1 1 0 0 0
0 0 1 1 1 1 1 1 1 0 0 0
0 0 1 1 1 1 1 1 1 0 0 0
0 0 1 1 1 1 1 1 1 0 0 0
0 0 1 1 1 1 1 1 1 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0출력 예
3
5
Cheese.h
#pragma once #include <iostream> #include <stack> #include "../../Base.h" using std::stack; class Cheese : public Base { private: struct Point { int x, y; Point(int x, int y) : x(x), y(y) {}; }; int GetCheeseMeltingCount(char** arr, int n, int m); bool IsCheese(char** arr, int n, int m, int x, int y); bool IsAir(char** arr, int n, int m, int x, int y); void CheckSideCheese(char** arr, int n, int m, int x, int y); void MeltCheese(char** arr, int n, int m); void RefreshAir(char** arr, int n, int m); int CountCheese(char** arr, int n, int m); protected: void Code() override; };
Cheese.cpp
void Cheese::Code() { int n, m; std::cin >> n >> m; char** arr = new char* [n]; for (int i = 0; i < n; i++) { arr[i] = new char[m]; for (int j = 0; j < m; j++) { std::cin >> arr[i][j]; } } int hour{ 0 }, count{ 0 }; while (true) { hour++; count = CountCheese(arr, n, m); int curCount{ GetCheeseMeltingCount(arr, n, m) }; if (curCount == 0) { break; } } std::cout << hour << '\n' << count; for (int i = 0; i < n; i++) { delete[] arr[i]; } delete[] arr; } /// <summary> /// 주어진 치즈가 녹는 시간과 마지막 개수를 출력한다. /// </summary> /// <param name="arr">배열</param> /// <param name="n">배열 세로 길이</param> /// <param name="m">배열 가로 길이</param> int Cheese::GetCheeseMeltingCount(char** arr, int n, int m) { for (int i = 0; i < m; i++) { // 배열의 위쪽 테두리 검사 if (IsCheese(arr, n, m, i, 0)) { arr[0][i] = 'c'; } else if (IsAir(arr, n, m, i, 0)) { CheckSideCheese(arr, n, m, i, 0); } // 배열의 아래쪽 테두리 검사 if (IsCheese(arr, n, m, i, n - 1)) { arr[n - 1][i] = 'c'; } else if (IsAir(arr, n, m, i, n - 1)) { CheckSideCheese(arr, n, m, i, n - 1); } // 배열의 왼쪽 테두리 검사 if (IsCheese(arr, n, m, 0, i)) { arr[i][0] = 'c'; } else if (IsAir(arr, n, m, 0, i)) { CheckSideCheese(arr, n, m, 0, i); } // 배열의 오른쪽 테두리 검사 if (IsCheese(arr, n, m, m - 1, i)) { arr[i][m - 1] = 'c'; } else if (IsAir(arr, n, m, m - 1, i)) { CheckSideCheese(arr, n, m, m - 1, i); } } MeltCheese(arr, n, m); RefreshAir(arr, n, m); return CountCheese(arr, n, m); } /// <summary> /// 해당 지점이 치즈인지 여부를 반환한다. /// </summary> /// <param name="arr">배열</param> /// <param name="n">배열 세로 길이</param> /// <param name="m">배열 가로 길이</param> /// <param name="x">확인할 x 좌표</param> /// <param name="y">확인할 y 좌표</param> /// <returns>치즈인지 여부</returns> bool Cheese::IsCheese(char** arr, int n, int m, int x, int y) { if (0 <= x && x < m && 0 <= y && y < n) { return arr[y][x] == '1'; } return false; } /// <summary> /// 해당 지점이 외부인지 여부를 반환한다. /// </summary> /// <param name="arr">배열</param> /// <param name="n">배열 세로 길이</param> /// <param name="m">배열 가로 길이</param> /// <param name="x">확인할 x 좌표</param> /// <param name="y">확인할 y 좌표</param> /// <returns>외부인지 여부</returns> bool Cheese::IsAir(char** arr, int n, int m, int x, int y) { if (0 <= x && x < m && 0 <= y && y < n) { return arr[y][x] == '0'; } return false; } /// <summary> /// 주어진 지점에 연결된 외곽 치즈를 찾아 표시한다. /// </summary> /// <param name="arr">배열</param> /// <param name="n">배열 세로 길이</param> /// <param name="m">배열 가로 길이</param> /// <param name="x">확인할 x 좌표</param> /// <param name="y">확인할 y 좌표</param> void Cheese::CheckSideCheese(char** arr, int n, int m, int x, int y) { stack<Point> s; s.push(Point(x, y)); while (!s.empty()) { Point p = s.top(); s.pop(); arr[p.y][p.x] = 'e'; for (int i = -1; i <= 1; i += 2) { if (IsCheese(arr, n, m, p.x + i, p.y)) { arr[p.y][p.x + i] = 'c'; } else if (IsAir(arr, n, m, p.x + i, p.y)) { s.push(Point(p.x + i, p.y)); } if (IsCheese(arr, n, m, p.x, p.y + i)) { arr[p.y + i][p.x] = 'c'; } else if (IsAir(arr, n, m, p.x, p.y + i)) { s.push(Point(p.x, p.y + i)); } } } } /// <summary> /// 배열 내 녹일 치즈를 제거한다. /// </summary> /// <param name="arr">배열</param> /// <param name="n">배열 세로 길이</param> /// <param name="m">배열 가로 길이</param> void Cheese::MeltCheese(char** arr, int n, int m) { for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { if (arr[i][j] == 'c') { arr[i][j] = '0'; } } } } /// <summary> /// 확인했던 외부 공기를 원래 상태로 돌린다. /// </summary> /// <param name="arr">배열</param> /// <param name="n">배열 세로 길이</param> /// <param name="m">배열 가로 길이</param> void Cheese::RefreshAir(char** arr, int n, int m) { for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { if (arr[i][j] == 'e') { arr[i][j] = '0'; } } } } /// <summary> /// 현재 남은 치즈의 개수를 반환한다. /// </summary> /// <param name="arr">배열</param> /// <param name="n">배열 세로 길이</param> /// <param name="m">배열 가로 길이</param> /// <returns>치즈의 개수</returns> int Cheese::CountCheese(char** arr, int n, int m) { int count{ 0 }; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { if (arr[i][j] == '1') { count++; } } } return count; }
실행 결과 Success(100)
코드해설
치즈 내부에서부터 외곽을 이동하면서 체크하는 것은 치즈 내부의 구멍을 구분하기 어렵기 때문에 외부의 공간에서 시작하여 외부와 직접 맞닿는 치즈를 체크하는 방식을 사용하면 쉽게 해결할 수 있다.
먼저 확실하게 공기인 부분인 배열의 테두리 부분을 시작 지점으로 삼는다.
이때, 배열의 테두리 부분에 치즈가 있는 경우를 걸러내기 위해 먼저 한 번 검사를 진행한다.
for (int i = 0; i < m; i++) { // 배열의 위쪽 테두리 검사 if (IsCheese(arr, n, m, i, 0)) { arr[0][i] = 'c'; } else if (IsAir(arr, n, m, i, 0)) { CheckSideCheese(arr, n, m, i, 0); } // 배열의 아래쪽 테두리 검사 if (IsCheese(arr, n, m, i, n - 1)) { arr[n - 1][i] = 'c'; } else if (IsAir(arr, n, m, i, n - 1)) { CheckSideCheese(arr, n, m, i, n - 1); } // 배열의 왼쪽 테두리 검사 if (IsCheese(arr, n, m, 0, i)) { arr[i][0] = 'c'; } else if (IsAir(arr, n, m, 0, i)) { CheckSideCheese(arr, n, m, 0, i); } // 배열의 오른쪽 테두리 검사 if (IsCheese(arr, n, m, m - 1, i)) { arr[i][m - 1] = 'c'; } else if (IsAir(arr, n, m, m - 1, i)) { CheckSideCheese(arr, n, m, m - 1, i); } }
이후 공기인 부분을 DFS로 검사를 진행하며 치즈를 발견하면 해당 부분을 체크한다.
void Cheese::CheckSideCheese(char** arr, int n, int m, int x, int y) { stack<Point> s; s.push(Point(x, y)); while (!s.empty()) { Point p = s.top(); s.pop(); arr[p.y][p.x] = 'e'; for (int i = -1; i <= 1; i += 2) { if (IsCheese(arr, n, m, p.x + i, p.y)) { arr[p.y][p.x + i] = 'c'; } else if (IsAir(arr, n, m, p.x + i, p.y)) { s.push(Point(p.x + i, p.y)); } if (IsCheese(arr, n, m, p.x, p.y + i)) { arr[p.y + i][p.x] = 'c'; } else if (IsAir(arr, n, m, p.x, p.y + i)) { s.push(Point(p.x, p.y + i)); } } } }
이후 체크했던 공기와 치즈를 모두 제거하고 남아있는 치즈의 수를 확인하여 반환한다.
MeltCheese(arr, n, m); RefreshAir(arr, n, m); return CountCheese(arr, n, m);
NadanKim/CodingTest_JUNGOL: JUNGOL 코딩 테스트를 위한 저장소 (github.com)