본문 바로가기
알고리즘/SW역량테스트 기출문제

[백준 14502번] 연구소 (조합, BFS)

by 지칸 2021. 4. 8.

삼성 SW 역량테스트 기출문제인 백준 사이트의 14502번 문제 연구소를 풀이해보겠습니다. 연구소 문제는 조합과 BFS를 사용하여 문제 풀이를 할 수 있습니다. 기존 17142번 연구소 3 문제와 유사한 것을 볼 수 있습니다. 본 포스팅이 난이도는 더 쉽습니다.


2021.04.08 - [알고리즘/SW역량테스트 기출문제] - [백준 14502번] 연구소 (조합, BFS)

2021.04.08 - [알고리즘/SW역량테스트 기출문제] - [백준 17142번] 연구소 3 (조합, BFS)

 

안녕하세요. 지칸입니다.

오늘 풀어볼 기출문제는 14502번 연구소입니다. 기존 17142번 연구소 3 문제와 흡사한 문제이며 난이도는 본 포스팅 문제가 더 쉽습니다. 기출 연도도 꽤 지난 문제입니다. 천천히 문제를 풀어보겠습니다.

 

1) 문제 이해

2) 구조 잡기

3) 주의 사항

 

저의  결론) 조합 + BFS 문제입니다. 연구소, 연구소 3은 문제가 매우 비슷합니다.


1) 문제 이해

문제 출처 : www.acmicpc.net/problem/14502

 

문제)

인체에 치명적인 바이러스를 연구하던 연구소에서 바이러스가 유출되었다. 다행히 바이러스는 아직 퍼지지 않았고, 바이러스의 확산을 막기 위해서 연구소에 벽을 세우려고 한다.

연구소는 크기가 N×M인 직사각형으로 나타낼 수 있으며, 직사각형은 1 ×1 크기의 정사각형으로 나누어져 있다. 연구소는 빈칸, 벽으로 이루어져 있으며, 벽은 칸 하나를 가득 차지한다. 

일부 칸은 바이러스가 존재하며, 이 바이러스는 상하좌우로 인접한 빈칸으로 모두 퍼져나갈 수 있다. 새로 세울 수 있는 벽의 개수는 3개이며, 꼭 3개를 세워야 한다.

예를 들어, 아래와 같이 연구소가 생긴 경우를 살펴보자.

0은 빈칸, 1은 벽, 2는 바이러스가 있는 곳이다. 아무런 벽을 세우지 않는다면, 바이러스는 모든 빈칸으로 퍼져나갈 수 있다.

벽을 3개 세운 뒤, 바이러스가 퍼질 수 없는 곳을 안전 영역이라고 한다. 위의 지도에서 안전 영역의 크기는 27이다.

연구소의 지도가 주어졌을 때 얻을 수 있는 안전 영역 크기의 최댓값을 구하는 프로그램을 작성하시오.

주어진 맵 2행 1열, 1행 2열, 4행 6열에 벽 추가 최종 결과
2 0 0 0 1 1 0
0 0 1 0 1 2 0
0 1 1 0 1 0 0
0 1 0 0 0 0 0
0 0 0 0 0 1 1
0 1 0 0 0 0 0
0 1 0 0 0 0 0
2 1 0 0 1 1 0
1 0 1 0 1 2 0
0 1 1 0 1 0 0
0 1 0 0 0 1 0
0 0 0 0 0 1 1
0 1 0 0 0 0 0
0 1 0 0 0 0 0
2 1 0 0 1 1 2
1 0 1 0 1 2 2
0 1 1 0 1 2 2
0 1 0 0 0 1 2
0 0 0 0 0 1 1
0 1 0 0 0 0 0
0 1 0 0 0 0 0


-> 문제 해석

  • 2차원 배열 NxM 맵 생성
  • 빈 칸 중 3개를 선택해서 벽을 생성 -> 조합 문제
  • 바이러스는 사방으로 확산 -> BFS문제
  • 최소 맵에서 빈칸 3개를 선택하는 방법, 경우의 수마다 맵이 필요 -> copy MAP 생성
  • 빈칸 수 카운팅, BFS 끝났을 때 빈칸수가 최대가 되는 경우 찾기 -> 이전까지 정답과 이번 정답과의 크기 비교

입력)

첫째 줄에 지도의 세로 크기 N과 가로 크기 M이 주어진다. (3 ≤ N, M ≤ 8)

둘째 줄부터 N개의 줄에 지도의 모양이 주어진다. 0은 빈칸, 1은 벽, 2는 바이러스가 있는 위치이다. 2의 개수는 2보다 크거나 같고, 10보다 작거나 같은 자연수이다.

빈칸의 개수는 3개 이상이다.

 

-> 입력 해석

  • 맵의 크기, 직사각형 구조, y축, x축 혼동하면 안 됨

출력)

첫째 줄에 얻을 수 있는 안전 영역의 최대 크기를 출력한다.

 

-> 출력 해석

  • 빈칸 중 3개 선택하는 모든 경우의 수를 진행하면서 max값 1개만 비교하며 계속 들고 가기

2) 구조 잡기

맨 처음 진행할 것은 입력 구조를 잡아주는 것입니다. 아래 포스팅에서 이미 설명을 한 적이 있습니다.

2019.04.25 - [알고리즘/SW역량테스트] - (1장-1) 역량 테스트 기본 템플릿 준비


2-1) 초기 세팅

이런 2차원 맵 문제는 거의 동일합니다. cin으로 input.txt를 받아와서 맵에 입력해 줍니다. 입력하면서 빈칸인지, 바이러스인지 체크하여 따로 좌표를 저장해 줍니다. 빈칸의 용도는 빈칸 중 3개 선택하기에 쓰이며, 바이러스의 용도는 BFS를 위한 큐에 입력해줄 때 사용됩니다.

#include <iostream>
#include <vector>
using namespace std;
#define MAX_N 8
#define MAX_M 8
#define VIRUS 2
#define WALL 1
#define EMPTY 0
struct coor
{
	int y;
	int x;
};
int N;
int M;
int originalmap[MAX_N][MAX_M] = { 0, };
vector<coor> virusarr;
vector<coor> emptyarr;
int sol = 0;

int main() {
	cin >> N >> M;
	int temp;
	coor tempcoor;
	for (int y = 0; y < N; y++) {
		for (int x = 0; x < M; x++) {
			cin >> temp;
			originalmap[y][x] = temp;
			if (temp == EMPTY) { //빈칸들 모아두기, 조합용
				tempcoor.y = y;
				tempcoor.x = x;
				emptyarr.push_back(tempcoor);				
			} 
			else if (temp == VIRUS) { //바이러스 모으기, 이것들로 BFS시작할 것
				tempcoor.y = y;
				tempcoor.x = x;
				virusarr.push_back(tempcoor);
			}
		}
	}

	//조합으로  총 빈칸 중 3개 뽑기

	cout << sol;
	return 0;
}

위 코딩은 문제없이 작성할 수 있습니다. 문제를 이해하면서 조합, BFS를 사용할 것이라는 걸 알 수 있었고 어디에 어떤 값들이 필요한지 파악한 상태입니다.

 

2-2) 조합 구현하기

이제 조합 알고리즘을 구현해 봅시다. 기존 조합 알고리즘 포스팅과 동일한 코드를 사용했습니다. 이 문제의 핵심은 BFS에 있습니다.

2021.04.01 - [알고리즘/SW역량테스트] - (4장-2) 조합(Combination) 알고리즘이란?

#define SELECTWALL 3
vector<int> flagComb;
vector<coor> emptyselectarr;

void operation() {
	// 조합 잘 나왔는 지 테스트
	for (int i = 0; i < emptyselectarr.size(); i++) { //항상 3
		cout << emptyselectarr[i].y << ", " << emptyselectarr[i].x << endl;
	}
	cout << endl;

	// 해당 경우에서 사용할 맵 복사 copy();
	// 3개의 벽 세우기 buildwall();
	// 바이러스 확산 BFS();
}
void comb(int n, int r) {
	if (n == r) {
		for (int i = 0; i < n; i++) {
			flagComb[i] = 1;
		}
		for (int i = 0; i < emptyarr.size(); i++) {
			if (flagComb[i] == 1) emptyselectarr.push_back(emptyarr[i]);
		}
		operation();
		emptyselectarr.clear();
		for (int i = 0; i < n; i++) {
			flagComb[i] = 0;
		}
		return;
	}
	if (r == 1) {
		for (int i = 0; i < n; i++) {
			flagComb[i] = 1;
			for (int j = 0; j < emptyarr.size(); j++) {
				if (flagComb[j] == 1) emptyselectarr.push_back(emptyarr[j]);
			}
			operation();
			emptyselectarr.clear();
			flagComb[i] = 0;
		}
		return;
	}
	flagComb[n - 1] = 1;
	comb(n - 1, r - 1);
	flagComb[n - 1] = 0;
	comb(n - 1, r);
}
int main() {
	// .. 기존 코드 생략 ..

	for (int i = 0; i < emptyarr.size(); i++) {
		flagComb.push_back(0);
	}
	//조합으로  총 빈칸 중 3개 뽑기
	comb(emptyarr.size(), SELECTWALL);
	cout << sol;
	return 0;
}

여러 문제를 풀다 보면, 조합에서는 변경될 곳이 거의 없다는 걸 알 수 있습니다.  nCr에서 n과 r의 배열만 만들어 주고 r개 선택 시 동작할 함수만 구현하면 조합 문제는 변화가 없습니다. 조합은 매우 쉬운 문제입니다.

 

우리가 사용할 벽 3개를 구했다면 이 경우에서 사용할 맵을 복사하고, 벽을 세우는 동작이 들어갑니다.

int emptycount = 0;
int copymap[MAX_N][MAX_M] = { 0, };

void copy() {
	emptycount = 0;
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < M; j++) {
			copymap[i][j] = originalmap[i][j];

			if (originalmap[i][j] == EMPTY) emptycount++;			
		}
	}
}
void buildwall() {
	for (int i = 0; i < SELECTWALL; i++) { //항상 3
		copymap[emptyselectarr[i].y][emptyselectarr[i].x] = WALL;	
		emptycount--;
	}
}

copy동작을 하면서 빈칸은 모두 카운팅을 하고 벽을 만들면서 3개는 빼주면 됩니다. 

 

2-3) BFS 구현하기

operation 함수에서 이제 BFS를 시작합니다. BFS 알고리즘과 거의 흡사한 틀을 보이고 있기 때문에 기존 BFS 알고리즘을 확실하게 익혀야 합니다.

2021.03.16 - [알고리즘/SW역량테스트] - (3장-2) BFS(너비우선탐색) 알고리즘이란?

 

#include <queue>

bool checkmaprange(coor yx) {
	if ((0 <= yx.y && yx.y < N) && (0 <= yx.x && yx.x < M))
		return true;
	return false;
}

void BFS() {
	queue<coor> q;
	for (int i = 0; i < virusarr.size(); i++) { //총 바이러스 수
		q.push(virusarr[i]);		
	}
	coor current;
	coor candidate;
	int dy[4] = { 0,-1,0,1 }; // 순서는 왼,위,오,아
	int dx[4] = { -1,0,1,0 };
	int count = emptycount; // 이번 조합에서 빈칸 카운팅
	while (!q.empty()) {
		current = q.front();
		for (int i = 0; i < MAX_Dir; i++) {
			candidate.y = current.y + dy[i];
			candidate.x = current.x + dx[i];
			if ((copymap[candidate.y][candidate.x] == EMPTY) && checkmaprange(candidate)) { //방문한적 없는 좌표, 
				q.push(candidate);
				copymap[candidate.y][candidate.x] = VIRUS; //바이러스 확산, 방문처리
				count--;
			}
		}
		q.pop();
	}
	if (sol < count) {
		sol = count; // 최대값만 유지
	}
}

위 코드를 보면 기존 BFS알고리즘과 거의 흡사한 것을 알 수 있습니다. 큐를 생성하고, 초기값으로 초기 바이러스를 전부 push 해줍니다. 그리고 저희는 빈칸이면 미방문, 그 외는 방문으로 대체하기 때문에 방문 표시는 별도로 할 필요가 없습니다. 

이런 좌표에서는 정점과 이어진 정점은 4방향밖에 없기 때문에 dy, dx를 통해 이어진 정점으로 접근할 수가 있습니다. 이건 2차원 맵 문제에서 자주 쓰이는 방식입니다. 이웃 정점이 빈칸이고 범위를 벗어난 건지 체크하고 큐에 이웃 정점을 추가합니다. 그리고 맵에 바이러스로 표기하여 방문했다는 것을 표기합니다. (빈칸 수도 1 감소)

 

큐가 없을 때까지 반복하면 while문을 종료합니다. 이는 바이러스 확산이 더 이상 일어날 수 없다는 의미입니다. 이 상태에서 빈칸수 count가 우리가 구하고자 하는 값입니다.


3) 주의 사항

이전에도 설명했지만 백준 문제는 테스트 케이스가 아닌 단일 입력에 대한 문제이기 때문에 초기화를 할 필요가 없습니다. 다만, 삼성 SW 역량테스트에서는 꼭 초기화에 신경 써주세요.

 

연구소 3과 약간의 차이를 두면서 문제를 풀어봤습니다. BFS에서 조건을 판별할 시 함수로 추출해도 괜찮고 아니어도 괜찮습니다. Isexceed 플래그로 불필요한 계산을 막아도 되고 안 해도 괜찮습니다. 최적화에 좀 더 신경 쓰고 싶으면 하는 걸 추천드립니다.

 

나머지는 코드에 주석으로 설명하고 있습니다. 혹시 이해 안 되는 부분이 있다면 댓글 주세요.




반응형

댓글