[BOJ] - 연구소 / 14502번 (Java)
1. 문제 접근
N x N 크기의 연구소에 2이상 10이하 개수를 갖는 바이러스가 있을 때, 총 3개의 벽을 빈 공간에 세워 얻을 수 있는 안전영역의 최대 크기를 구하면 된다.
먼저 입력(N)의 크기가 최대 8로 크지 않다.
완전 탐색을 통해 나이브하게 경우의 수를 살펴 보면 64 Combination 3 (벽을 선택하는 경우의 수) x 바이러스를 퍼트리는 시간(<64) 이므로 300만을 넘지 않는다.
따라서 완전 탐색으로 구현하였다.
먼저 빈 공간의 위치 정보를 `Pos`형 일차원 배열에 저장했다.
if (map[row][col] == 0) {
// 빈 공간 위치 정보 저장
elementList[elementCnt++] = new Pos(row, col);
}
그 후 Combination을 통해 `elementList[]`에서 3개의 위치를 선정했다.
따라서 재귀로 구현한 조합의 기저조건은 `selectIdx == 3`이 될 때가 된다.
해당 조합에 대해 1. 벽을 세우고, 2.바이러스를 퍼트리고, 3. 다시 벽을 허무는 과정을 거치게 된다.
바이러스를 퍼트렸다면 안전영역의 개수를 구해야한다.
벽은 빈 공간에 세우므로 기존의 안전 영역의 크기에서 3을 빼야한다.
또한 기존의 안전영역을 바이러스가 침범하되, 원래 초기 위치의 바이러스 위치는 기존의 안전영역으로 카운팅 되지 않았으므로 추가적으로 뺀 영역의 크기만큼 더해준다.
if (selectIdx == WALL_NUMBER) {
// 선정한 위치 벽 세우기
for (Pos wallPos : selectList)
map[wallPos.x][wallPos.y] = 1;
int virusArea = countVirusArea();
// 선정한 위치 벽 허물기
for (Pos wallPos : selectList)
map[wallPos.x][wallPos.y] = 0;
// 안전 영역크기 계산 후 갱신
// 빈공간 - 벽의 개수(3) - 바이러스 영역 + 기존에 바이러스가 존재하는 영역의 수
ans = Math.max(ans, emtpySpace - 3 - virusArea + virusSpace);
return;
}
이후 기존의 안전영역의 크기와 비교하여 갱신하게 된다.
2. 코드
package boj;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.StringTokenizer;
/**
* [BOJ] - 연구소 / 14502번
* @author JinHxxxxKim
*
* 1. 행, 열에 대해 3개를 선택하는 조합으로 완전 탐색 진행
* 2. 해당 좌표가 이미 벽, 바이러스라면 PASS
* 2-1. 기저조건(3개 모두 선택했다면): 바이러스에 대해 BFS 실행 후, 남아있는 영역 계산
* 2-2. 안전 지대 영역의 수 갱신
*/
public class Sol_14502 {
private static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
private static StringTokenizer st;
static final int[] dx = new int[] { -1, 0, 1, 0 };
static final int[] dy = new int[] { 0, 1, 0, -1 };
static final int WALL_NUMBER = 3;
static int rowMax, colMax;
static int[][] map;
static int emtpySpace, virusSpace;
static int ans;
static Pos[] initVirusPos;
// 조합 관련 변수
static Pos[] selectList;
static Pos[] elementList;
public static void main(String[] args) throws Exception {
st = new StringTokenizer(br.readLine().trim());
rowMax = Integer.parseInt(st.nextToken());
colMax = Integer.parseInt(st.nextToken());
map = new int[rowMax][colMax];
selectList = new Pos[WALL_NUMBER];
// 맵 정보 입력
for (int row = 0; row < rowMax; ++row) {
st = new StringTokenizer(br.readLine().trim());
for (int col = 0; col < colMax; ++col) {
map[row][col] = Integer.parseInt(st.nextToken());
if (map[row][col] == 0) {
// 빈 공간 개수 확인
emtpySpace++;
} else if (map[row][col] == 2) {
// 바이러스 개수 확인
virusSpace++;
}
}
}
int elementCnt = 0;
int virusCnt = 0;
elementList = new Pos[emtpySpace];
initVirusPos = new Pos[virusSpace];
for (int row = 0; row < rowMax; ++row) {
for (int col = 0; col < colMax; ++col) {
if (map[row][col] == 0) {
// 빈 공간 위치 정보 저장
elementList[elementCnt++] = new Pos(row, col);
} else if (map[row][col] == 2) {
// 바이러스 위치 정보 저장
initVirusPos[virusCnt++] = new Pos(row, col);
}
}
}
ans = 0;
selectWall(0, 0);
System.out.println(ans);
}
/**
* 벽 세울 위치를 정하는 메소드
* @param selectIdx 선택할 인덱스
* @param elementIdx 현재 조회하는 요소의 인덱스
*/
private static void selectWall(int selectIdx, int elementIdx) {
// 기저조건
if (selectIdx == WALL_NUMBER) {
// 선정한 위치 벽 세우기
for (Pos wallPos : selectList)
map[wallPos.x][wallPos.y] = 1;
int virusArea = countVirusArea();
// 선정한 위치 벽 허물기
for (Pos wallPos : selectList)
map[wallPos.x][wallPos.y] = 0;
// 안전 영역크기 계산 후 갱신
// 빈공간 - 벽의 개수(3) - 바이러스 영역 + 기존에 바이러스가 존재하는 영역의 수
ans = Math.max(ans, emtpySpace - 3 - virusArea + virusSpace);
return;
}
if (elementIdx == emtpySpace) {
return;
}
// 해당 좌표 선택O
selectList[selectIdx] = elementList[elementIdx];
selectWall(selectIdx + 1, elementIdx + 1);
// 해당 좌표 선택X
selectWall(selectIdx, elementIdx + 1);
}
/**
* 바이러스가 퍼질 수 있는 영역의 수를 구하는 메소드
* @return 바이러스가 퍼질 수 있는 영역의 수
*/
private static int countVirusArea() {
int ret = 0;
Queue<Pos> q = new ArrayDeque<>();
boolean[][] isVisited = new boolean[rowMax][colMax];
// 초기 바이러스 위치 정보 삽입
for (Pos initVirus : initVirusPos) {
q.offer(initVirus);
isVisited[initVirus.x][initVirus.y] = true;
}
while (!q.isEmpty()) {
Pos currNode = q.poll();
ret++;
int currRow = currNode.x;
int currCol = currNode.y;
// 4방 탐색
for (int dir = 0; dir < 4; ++dir) {
int tempRow = currRow + dx[dir];
int tempCol = currCol + dy[dir];
// 범위 검증
if (tempRow < 0 || tempCol < 0 || tempRow >= rowMax || tempCol >= colMax)
continue;
// 방문 검증
if (isVisited[tempRow][tempCol])
continue;
// 벽 검증
if (map[tempRow][tempCol] == 1)
continue;
isVisited[tempRow][tempCol] = true;
q.offer(new Pos(tempRow, tempCol));
}
}
return ret;
}
// 위치 클래스
static class Pos {
int x, y;
public Pos(int x, int y) {
super();
this.x = x;
this.y = y;
}
}
}
3. 디버깅
입력값의 크기가 작은 것을 보고 완전탐색에 대한 아이디어를 떠올릴 수 있어 바로 접근할 수 있었던 문제였다.
안전영역의 개수를 계산할 때, 기존 바이러스의 초기위치를 추가적으로 빼는 부분을 조심하여 구현하였다.
4. 결과
'알고리즘' 카테고리의 다른 글
[BOJ] - 아기 상어 / 16236번 (Java) (0) | 2024.02.27 |
---|---|
[BOJ] - 외판원 순회 2 / 10971번 (Java) (1) | 2024.02.26 |
[BOJ] - 거짓말 / 1043번 (Java) (1) | 2024.02.25 |
[BOJ] - 임계경로 / 1948번 (Java) (0) | 2024.02.25 |
[BOJ] - 게임 개발 / 1516번 (Java) (0) | 2024.02.23 |