이번에는 백준 14502번 연구소 문제를 풀어보았습니다.
이 문제는 연구소의 빈칸 중 정확히 3곳에 벽을 세운 뒤, 바이러스가 퍼지고 남는 안전 영역의 최대 크기를 구하는 문제입니다.
핵심은 크게 두 가지였습니다.
즉, 벽을 세울 위치를 모두 시도해보고, 각 경우마다 바이러스 확산 결과를 계산하는 방식으로 해결할 수 있었습니다.
연구소는 N x M 크기의 격자로 주어지고, 각 칸은 다음 중 하나입니다.
0 : 빈 칸1 : 벽2 : 바이러스새로 세울 수 있는 벽은 정확히 3개이고,
바이러스는 상하좌우 인접한 빈 칸으로 퍼질 수 있습니다.
벽을 3개 세운 뒤 바이러스가 모두 퍼지고 나서,
퍼지지 않고 남아 있는 빈 칸의 개수 중 최댓값을 구하면 됩니다.
이 문제는 모든 빈칸 중에서 3개를 고르는 경우를 전부 시도해보면 됩니다.
그래서 먼저 빈칸 위치들을 전부 empty_cells에 저장해두고,
이 중 3개를 고르는 조합을 3중 반복문으로 만들었습니다.
그리고 각 조합마다
방식으로 구현했습니다.
즉, 전체 구조는
벽 3개 선택 → 바이러스 확산 → 안전 영역 계산 → 원상복구
로 볼 수 있습니다.
#include <bits/stdc++.h>
using namespace std;
int N,M;
int max_cnt;
int dy[4] = {0,1,0,-1};
int dx[4] = {1,0,-1,0};
vector<vector<int>> inp_map;
vector<vector<int>> visited;
vector<pair<int, int>> empty_cells;
void dfs(int y, int x) {
visited[y][x] = 1;
for (int i = 0; i < 4; i++) {
int ny = y + dy[i];
int nx = x + dx[i];
if (ny < 0 || nx < 0 || ny >= N || nx >= M || visited[ny][nx]) continue;
if (inp_map[ny][nx] == 1)
continue;
dfs(ny, nx);
}
}
void solve(pair<int, int> first, pair<int, int> second, pair<int, int> third) {
inp_map[first.first][first.second] = 1;
inp_map[second.first][second.second] = 1;
inp_map[third.first][third.second] = 1;
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
if (inp_map[i][j] == 2 && visited[i][j] == 0) {
dfs(i, j);
}
}
}
int cnt = 0;
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
if (inp_map[i][j] == 2 || visited[i][j] == 1 || inp_map[i][j] == 1) continue;
cnt++;
}
}
if (cnt > max_cnt) max_cnt = cnt;
for (int i = 0; i < visited.size(); i++) {
fill(visited[i].begin(), visited[i].end(), 0);
}
inp_map[first.first][first.second] = 0;
inp_map[second.first][second.second] = 0;
inp_map[third.first][third.second] = 0;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
cin >> N >> M;
for (int i = 0; i < N; i++) {
inp_map.push_back(vector<int>());
visited.push_back(vector<int>());
for (int j = 0; j < M; j++) {
int temp;
cin >> temp;
inp_map[i].push_back(temp);
visited[i].push_back(0);
if (temp == 0)
empty_cells.push_back(make_pair(i, j));
}
}
for (int i=0; i<empty_cells.size(); i++) {
for (int j=i+1; j<empty_cells.size(); j++) {
for (int k=j+1; k<empty_cells.size(); k++) {
solve(empty_cells[i], empty_cells[j], empty_cells[k]);
}
}
}
cout << max_cnt << endl;
return 0;
}
empty_cells에 저장한다.solve()를 호출한다.solve()에서는 선택한 세 칸을 벽으로 만든다.visited로 표시한다.visited를 초기화하고, 세웠던 벽도 다시 빈칸으로 되돌린다.이 문제는 벽을 세울 수 있는 위치가 빈칸뿐이기 때문에,
처음 입력을 받을 때 빈칸 좌표를 따로 모아두었습니다.
if (temp == 0)
empty_cells.push_back(make_pair(i, j));
이렇게 해두면 이후에는 전체 맵을 다시 보지 않고,
빈칸 목록만 가지고 벽을 세울 후보를 선택할 수 있습니다.
벽은 정확히 3개를 세워야 하므로,
empty_cells 중에서 3개를 고르는 조합을 만들었습니다.
for (int i=0; i<empty_cells.size(); i++) {
for (int j=i+1; j<empty_cells.size(); j++) {
for (int k=j+1; k<empty_cells.size(); k++) {
solve(empty_cells[i], empty_cells[j], empty_cells[k]);
}
}
}
이 구조를 통해 같은 위치를 중복해서 고르지 않으면서,
가능한 모든 3개 조합을 확인할 수 있습니다.
즉, 이 문제에서 벽 배치는 조합 문제로 볼 수 있습니다.
solve()에 들어오면 먼저 선택한 세 위치를 벽으로 바꿉니다.
inp_map[first.first][first.second] = 1;
inp_map[second.first][second.second] = 1;
inp_map[third.first][third.second] = 1;
이렇게 해서 현재 조합에 대해 실제로 벽을 세운 상태를 만든 뒤,
그 상태에서 바이러스 확산을 계산합니다.
이후 맵 전체를 돌면서 바이러스인 칸(2)에서 DFS를 시작합니다.
if (inp_map[i][j] == 2 && visited[i][j] == 0) {
dfs(i, j);
}
DFS에서는 벽만 막고, 나머지 칸으로 계속 퍼질 수 있도록 했습니다.
if (inp_map[ny][nx] == 1)
continue;
dfs(ny, nx);
즉, visited는 현재 벽 배치에서 바이러스가 도달 가능한 칸들을 표시하는 역할을 합니다.
DFS가 끝난 뒤에는 안전 영역 개수를 셉니다.
if (inp_map[i][j] == 2 || visited[i][j] == 1 || inp_map[i][j] == 1) continue;
cnt++;
여기서 세지 않는 칸은
입니다.
즉, 벽도 아니고, 바이러스도 아니고, 감염도 안 된 칸만 세어서 안전 영역 크기를 구합니다.
한 조합 계산이 끝나면 다음 조합을 위해 상태를 다시 되돌려야 합니다.
먼저 visited를 전부 0으로 초기화합니다.
for (int i = 0; i < visited.size(); i++) {
fill(visited[i].begin(), visited[i].end(), 0);
}
그리고 방금 세웠던 세 개의 벽도 다시 빈칸으로 바꿉니다.
inp_map[first.first][first.second] = 0;
inp_map[second.first][second.second] = 0;
inp_map[third.first][third.second] = 0;
이 과정이 있어야 다음 조합을 독립적으로 다시 시도할 수 있습니다.