[BOJ] 14502 - 연구소

Sierra·2022년 2월 12일
0

[BOJ] GraphTheory

목록 보기
17/30
post-thumbnail

https://www.acmicpc.net/problem/14502

문제

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

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

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

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

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

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

2행 1열, 1행 2열, 4행 6열에 벽을 세운다면 지도의 모양은 아래와 같아지게 된다.

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

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

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

입력

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

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

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

출력

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

예제 입출력

  • 예제 입력 1
7 7
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
  • 예제 출력 1
27
  • 예제 입력 2
4 6
0 0 0 0 0 0
1 0 0 0 0 2
1 1 1 0 0 2
0 0 0 0 0 2
  • 예제 출력 2
9
  • 예제 입력 3
8 8
2 0 0 0 0 0 0 2
2 0 0 0 0 0 0 2
2 0 0 0 0 0 0 2
2 0 0 0 0 0 0 2
2 0 0 0 0 0 0 2
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
  • 예제 출력 3
3

Solution

처음에는 각 바이러스 근처마다 벽을 세워 보았다. 코드는 드럽게 복잡해지고 문제가 풀리는것과는 다르게 반례를 너무나 많이 찾아내고 극복해야 했었다.
데이터의 범위가 적기 때문에 완전탐색을 쓰는 게 훨씬 효율적이다.

#include <iostream>
#include <queue>
#include <vector>
using namespace std;

int dx[4] = {0,0,1,-1};
int dy[4] = {1,-1,0,0};
int N, M;
int answer = 0;
void BFS(vector<vector<int>> & vec){
    queue<pair<int, int>> Q;
    int count = 0;
    for(int i = 0; i < N; i++){
        for(int j = 0; j < M; j++){
            if(vec[i][j] == 2) Q.push({i, j});
        }
    }
    while(!Q.empty()){
        int X = Q.front().first;
        int Y = Q.front().second;
        Q.pop();
        for(int i = 0; i < 4; i++){
            int nx = X + dx[i];
            int ny = Y + dy[i];
            if(nx >= 0 && nx < N && ny >= 0 && ny < M && vec[nx][ny] == 0){
                vec[nx][ny] = 2;
                Q.push({nx, ny});
            }
        }
    }
    for(int i = 0; i < N; i++){
        for(int j = 0; j < M; j++){
            if(vec[i][j] == 0) count++;
        }
    }
    answer = max(answer, count);
}

void Find(vector<vector<int>> vec, int x, int y, int count){
    vec[x][y] = 1;
    if(count == 0){
        BFS(vec);
        return;
    }
    for(int i = x ; i < N ; i++){
        for(int j = 0; j < M; j++){
            if(vec[i][j] == 0) Find(vec, i, j, count -1);
        }
    }
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> N >> M;
    vector<vector<int>> vec(N, vector<int>(M));
    for(int i = 0; i < N; i++){
        for(int j = 0; j < M; j++){
            cin >> vec[i][j];
        }
    }
    for(int i = 0; i < N; i++){
        for(int j = 0; j < M; j++){
            if(vec[i][j] == 0) Find(vec, i, j, 2);
        }
    }
    cout << answer << '\n';
    return 0;
}

여러 번 탐색이 진행되어야 한다. 기존에는 전역 변수로 인접행렬, 좌표배열 등을 처리했다면 이번에는 여러 번 지웠다 썼다 하는 게 번거로울 수 있으니까 그냥 매개변수로 처리한다.

요약 하자면 지도 상에 벽을 놓을 수 있는 모든 경우에 대해 놓아보고 안전 영역이 가장 넓은 경우를 찾는 것이다.

cin >> N >> M;
vector<vector<int>> vec(N, vector<int>(M));
for(int i = 0; i < N; i++){
    for(int j = 0; j < M; j++){
        cin >> vec[i][j];
    }
}
for(int i = 0; i < N; i++){
    for(int j = 0; j < M; j++){
        if(vec[i][j] == 0) Find(vec, i, j, 2);
    }
}

모든 데이터를 vector 형태로 입력받는다. 2차원 배열 형태로도 해 보았고 딱히 차이는 없다. 어차피 행과 열 길이 데이터 N, M 이 있기 때문에.
그 후 모든 데이터를 뒤져가며 0인 곳 기준으로 탐색을 시작한다.

void Find(vector<vector<int>> vec, int x, int y, int count){
    vec[x][y] = 1;
    if(count == 0){
        BFS(vec);
        return;
    }
    for(int i = x ; i < N ; i++){
        for(int j = 0; j < M; j++){
            if(vec[i][j] == 0) Find(vec, i, j, count -1);
        }
    }
}

우선 탐색하고자 하는 기준 점에 벽을 세운다(1로 마커 처리) 그 후 해당 행 부터 나머지 데이터들을 뒤져서 0이 나온다면 재귀호출을 통해 다시 1로 마커 처리 해준다. 이렇게 하는 이유는 벽을 세워야 할 경우가 너무나 많기 때문에 해당 행 보다 위로는 벽을 쌓지 않으려는 것이다. 즉 위에서 아래로 탐색하기 위해서다.

void BFS(vector<vector<int>> & vec){
    queue<pair<int, int>> Q;
    int count = 0;
    for(int i = 0; i < N; i++){
        for(int j = 0; j < M; j++){
            if(vec[i][j] == 2) Q.push({i, j});
        }
    }
    while(!Q.empty()){
        int X = Q.front().first;
        int Y = Q.front().second;
        Q.pop();
        for(int i = 0; i < 4; i++){
            int nx = X + dx[i];
            int ny = Y + dy[i];
            if(nx >= 0 && nx < N && ny >= 0 && ny < M && vec[nx][ny] == 0){
                vec[nx][ny] = 2;
                Q.push({nx, ny});
            }
        }
    }
    for(int i = 0; i < N; i++){
        for(int j = 0; j < M; j++){
            if(vec[i][j] == 0) count++;
        }
    }
    answer = max(answer, count);
}

그 후 모든 바이러스 스팟에 대해 탐색을 진행하며 BFS를 통해 바이러스를 퍼트려본다. 그 후 최종 안전영역이 얼마인지 확인하고 최댓값을 갱신한다.

이렇게 모든 경우에 대해 탐색을 반복한다.

데이터가 커 봐야 8 X 8 2차원 데이터라 이렇게 탐색을 해도 실행시간이 오래 걸리지 않는다. 가장 어려웠던 게 벽을 어디에 붙혀야 하나였는데 또 다른 방법을 하나 배운 문제였다.

profile
블로그 이전합니다 : https://swj-techblog.vercel.app/

0개의 댓글