알고리즘 문제

백준 17142번) 연구소3(JAVA)

roder 2022. 8. 5. 14:33

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

 

17142번: 연구소 3

인체에 치명적인 바이러스를 연구하던 연구소에 승원이가 침입했고, 바이러스를 유출하려고 한다. 바이러스는 활성 상태와 비활성 상태가 있다. 가장 처음에 모든 바이러스는 비활성 상태이고

www.acmicpc.net

 

#문제

인체에 치명적인 바이러스를 연구하던 연구소에 승원이가 침입했고, 바이러스를 유출하려고 한다. 바이러스는 활성 상태와 비활성 상태가 있다. 가장 처음에 모든 바이러스는 비활성 상태이고, 활성 상태인 바이러스는 상하좌우로 인접한 모든 빈 칸으로 동시에 복제되며, 1초가 걸린다. 승원이는 연구소의 바이러스 M개를 활성 상태로 변경하려고 한다.

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

예를 들어, 아래와 같이 연구소가 생긴 경우를 살펴보자. 0은 빈 칸, 1은 벽, 2는 바이러스의 위치이다.

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 2 0 1 1
0 1 0 0 0 0 0
2 1 0 0 0 0 2

M = 3이고, 바이러스를 아래와 같이 활성 상태로 변경한 경우 6초면 모든 칸에 바이러스를 퍼뜨릴 수 있다. 벽은 -, 비활성 바이러스는 *, 활성 바이러스는 0, 빈 칸은 바이러스가 퍼지는 시간으로 표시했다.

* 6 5 4 - - 2
5 6 - 3 - 0 1
4 - - 2 - 1 2
3 - 2 1 2 2 3
2 2 1 0 1 - -
1 - 2 1 2 3 4
0 - 3 2 3 4 *

시간이 최소가 되는 방법은 아래와 같고, 4초만에 모든 칸에 바이러스를 퍼뜨릴 수 있다.

0 1 2 3 - - 2
1 2 - 3 - 0 1
2 - - 2 - 1 2
3 - 2 1 2 2 3
3 2 1 0 1 - -
4 - 2 1 2 3 4
* - 3 2 3 4 *

연구소의 상태가 주어졌을 때, 모든 빈 칸에 바이러스를 퍼뜨리는 최소 시간을 구해보자.

 

#알고리즘

브루트포스 알고리즘

그래프 이론

그래프 탐색

너비 우선 탐색

 

#난이도

Gold 4

 

#풀이방법

사실 너비 우선 탐색 문제는 많이 풀어봤기에 문제 자체를 구현하는 것에는 어려움이 없었다. 그러나 이 문제에서는 까다로운 조건들이 조금(?) 추가돼서  그 조건들을 맞추면서 구현하는 것에 많은 어려움이 있었다.

 

조건 1.  비활성 바이러스 중 조합을 이용하여 M개의 활성 바이러스를 활성화 시키는 것 

조건 2.  활성 바이러스 ,비활성 바이러스 , 벽, 빈 공간등 기존에 랭킹이 낮은 문제들보다 구분해야할 등장요소들이 많은 것

 

 

나 같은 경우는 기존 유형에 문제에 익숙해져서 그런지  조합을 사용해서 비활성 바이러스를 활성화 시키는 것이 아닌 

백트래킹으로 이를 구현하려고 하다보니 원하는데로 바이러스가 활성이 되지 않아 많은 어려움을 겪었다. 

간신히 조합 코드를 구글링하여 비활성 바이러스를 활성했다....

기존에 내 방식을 추구하는 것이 아닌 문제의 요구조건에 따라 유도리 있게 코드를 짜야하는 것임을 다시 한번 상기시켰다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
}
    
    public static void combination(boolean[] visited, int start, int n, int r) {
       
        if (r == 0) {
            
            Virus[] active_v = new Virus[M];
            int idx = 0;
            
            for(int i=0; i<v_list.size(); i++) {
                
                if(visited[i]) {
                    active_v[idx++= v_list.get(i);
                }//방문처리된 배열 요소를 활성화된 v_list에 넣어줌
                
            }
            
            //활성된 바이러스를 너비우선으로 탐색한다.
            back_track(active_v);
            
            return;
        }
 
        for (int i = start; i < n; i++) {
            
            visited[i] = true;
            combination(visited, i + 1, n, r - 1);
            visited[i] = false;
            
        }
        
        
    }//백트래킹을 사용한 조합 코드
cs

 

그 후 활성화된 바이러스를 queue에 넣은 후, 벽과 활성화된 바이러스를 방문처리 한다.  빈 벽을 카운트 한후 시뮬레이션을 돌려 빈벽이 0인 경우, min 최소값을 비교한후 브루트포스 알고리즘으로 모두 다 비교한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
public static void back_track(Virus[] v) {
        
        Queue<Virus> queue = new LinkedList<>();
        boolean[][] visited = new boolean[N][N];
        int empty = 0;
        
        for(int i=0; i<v.length; i++) {
            queue.add(v[i]);
            visited[v[i].y][v[i].x] = true;
        }//활성화된 바이러스를 queue에 추가
        
        for(int i=0; i<N; i++) {
            for(int j=0; j<N; j++) {
                
                if(map[i][j] == 1) {
                    visited[i][j] = true;
                }//벽일 경우 방문처리
                else if(map[i][j] == 0) {
                    empty++;
                }//빈벽이면 추가시켜줌
                
            }
        }//벽은 방문처리
        
        if(empty == 0) {
            min = 0;
            return;
        }//빈벽이 아예 없다면 시간은 0초
        
        
        while(!queue.isEmpty()){
            
            Virus vir = queue.poll();
            
            
            for(int i=0; i<4; i++) {
                
                if(0 > vir.y+dy[i] || N <= vir.y+dy[i] || 0 > vir.x+dx[i] || N <= vir.x+dx[i])
                    continue;
                //범위에서 벗어나거나
                if(visited[vir.y+dy[i]][vir.x+dx[i]])
                    continue;
                //이미 방문한 경우, continue 처리
                
                queue.add(new Virus(vir.y+dy[i],vir.x+dx[i],vir.count+1));
                visited[vir.y+dy[i]][vir.x+dx[i]] = true;
                //방문 처리
                
                if(map[vir.y+dy[i]][vir.x+dx[i]] == 0)
                    empty--;//빈벽인 경우 하나씩 빼줌
                
                if(empty == 0) {
                    
                    if(vir.count < min)
                        min = vir.count+1;
                    
                    break;
                    
                }//빈벽이 없는 경우 모두 감염된 것이니 최소값을 비교한후 끝낸다.
                
            }
            
        }
        
        
    }
    
    public static void main(String[] args) {
        
        Scanner sc = new Scanner(System.in);
        
        N = sc.nextInt();
        map = new int[N][N];
        
        M = sc.nextInt();
        
        for(int i=0; i<N; i++) {
            for(int j=0; j<N; j++) {
                
                map[i][j] = sc.nextInt();
                
                if(map[i][j] == 2) {
                    v_num++;
                    v_list.add(new Virus(i,j,0));
                }//바이러스 리스트
            }
        }
        
        boolean[] visited = new boolean[v_num];
        
        combination(visited,0,v_num,M);
        
        
        
        if(min == Integer.MAX_VALUE)
            System.out.println(-1);
        else
            System.out.println(min);
        
    }
cs

 

#전체 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import java.util.*;
import java.io.*;
//1t
 
class Main {
    
    static int[] dy = {1,0,-1,0};
    static int[] dx  = {0,1,0,-1};
    static int N,M;
    static int[][] map;
    static int v_num;
    static ArrayList<Virus> v_list = new ArrayList<>();
    static int min = Integer.MAX_VALUE;
    
    public static void back_track(Virus[] v) {
        
        Queue<Virus> queue = new LinkedList<>();
        boolean[][] visited = new boolean[N][N];
        int empty = 0;
        
        for(int i=0; i<v.length; i++) {
            queue.add(v[i]);
            visited[v[i].y][v[i].x] = true;
        }//활성화된 바이러스를 queue에 추가
        
        for(int i=0; i<N; i++) {
            for(int j=0; j<N; j++) {
                
                if(map[i][j] == 1) {
                    visited[i][j] = true;
                }//벽일 경우 방문처리
                else if(map[i][j] == 0) {
                    empty++;
                }//빈벽이면 추가시켜줌
                
            }
        }//벽은 방문처리
        
        if(empty == 0) {
            min = 0;
            return;
        }//빈벽이 아예 없다면 시간은 0초
        
        
        while(!queue.isEmpty()){
            
            Virus vir = queue.poll();
            
            
            for(int i=0; i<4; i++) {
                
                if(0 > vir.y+dy[i] || N <= vir.y+dy[i] || 0 > vir.x+dx[i] || N <= vir.x+dx[i])
                    continue;
                //범위에서 벗어나거나
                if(visited[vir.y+dy[i]][vir.x+dx[i]])
                    continue;
                //이미 방문한 경우, continue 처리
                
                queue.add(new Virus(vir.y+dy[i],vir.x+dx[i],vir.count+1));
                visited[vir.y+dy[i]][vir.x+dx[i]] = true;
                //방문 처리
                
                if(map[vir.y+dy[i]][vir.x+dx[i]] == 0)
                    empty--;//빈벽인 경우 하나씩 빼줌
                
                if(empty == 0) {
                    
                    if(vir.count < min)
                        min = vir.count+1;
                    
                    break;
                    
                }//빈벽이 없는 경우 모두 감염된 것이니 최소값을 비교한후 끝낸다.
                
            }
            
        }
        
        
    }
    
    public static void main(String[] args) {
        
        Scanner sc = new Scanner(System.in);
        
        N = sc.nextInt();
        map = new int[N][N];
        
        M = sc.nextInt();
        
        for(int i=0; i<N; i++) {
            for(int j=0; j<N; j++) {
                
                map[i][j] = sc.nextInt();
                
                if(map[i][j] == 2) {
                    v_num++;
                    v_list.add(new Virus(i,j,0));
                }//바이러스 리스트
            }
        }
        
        boolean[] visited = new boolean[v_num];
        
        combination(visited,0,v_num,M);
        
        
        
        if(min == Integer.MAX_VALUE)
            System.out.println(-1);
        else
            System.out.println(min);
        
    }
    
    public static void combination(boolean[] visited, int start, int n, int r) {
       
        if (r == 0) {
            
            Virus[] active_v = new Virus[M];
            int idx = 0;
            
            for(int i=0; i<v_list.size(); i++) {
                
                if(visited[i]) {
                    active_v[idx++= v_list.get(i);
                }//방문처리된 배열 요소를 활성화된 v_list에 넣어줌
                
            }
            
            //활성된 바이러스를 너비우선으로 탐색한다.
            back_track(active_v);
            
            return;
        }
 
        for (int i = start; i < n; i++) {
            
            visited[i] = true;
            combination(visited, i + 1, n, r - 1);
            visited[i] = false;
            
        }
        
        
    }//백트래킹을 사용한 조합 코드
 
    
}
 
 
 
class Virus{
    
    int y,x,count;
    
    Virus(int y,int x,int count){
        this.y = y;
        this.x= x;
        this.count = count;
    }
 
    
}
 
cs