#3109 빵집
난이도 : 골드2
유형 : 그리디 / 그래프 탐색 / DFS
▸ 문제
유명한 제빵사 김원웅은 빵집을 운영하고 있다. 원웅이의 빵집은 글로벌 재정 위기를 피해가지 못했고, 결국 심각한 재정 위기에 빠졌다.
원웅이는 지출을 줄이고자 여기저기 지출을 살펴보던 중에, 가스비가 제일 크다는 것을 알게되었다. 따라서 원웅이는 근처 빵집의 가스관에 몰래 파이프를 설치해 훔쳐서 사용하기로 했다.
빵집이 있는 곳은 R*C 격자로 표현할 수 있다. 첫째 열은 근처 빵집의 가스관이고, 마지막 열은 원웅이의 빵집이다.
원웅이는 가스관과 빵집을 연결하는 파이프를 설치하려고 한다. 빵집과 가스관 사이에는 건물이 있을 수도 있다. 건물이 있는 경우에는 파이프를 놓을 수 없다.
가스관과 빵집을 연결하는 모든 파이프라인은 첫째 열에서 시작해야 하고, 마지막 열에서 끝나야 한다. 각 칸은 오른쪽, 오른쪽 위 대각선, 오른쪽 아래 대각선으로 연결할 수 있고, 각 칸의 중심끼리 연결하는 것이다.
원웅이는 가스를 되도록 많이 훔치려고 한다. 따라서, 가스관과 빵집을 연결하는 파이프라인을 여러 개 설치할 것이다. 이 경로는 겹칠 수 없고, 서로 접할 수도 없다. 즉, 각 칸을 지나는 파이프는 하나이어야 한다.
원웅이 빵집의 모습이 주어졌을 때, 원웅이가 설치할 수 있는 가스관과 빵집을 연결하는 파이프라인의 최대 개수를 구하는 프로그램을 작성하시오.
▸ 입력
첫째 줄에 R과 C가 주어진다. (1 ≤ R ≤ 10,000, 5 ≤ C ≤ 500)
다음 R개 줄에는 빵집 근처의 모습이 주어진다. '.'는 빈 칸이고, 'x'는 건물이다. 처음과 마지막 열은 항상 비어있다.
▸ 출력
첫째 줄에 원웅이가 놓을 수 있는 파이프라인의 최대 개수를 출력한다.
문제 풀이
모든 경로를 탐색해줘서 파이프 연결 갯수를 구해주면 될 것 같지만 맵의 최대 크기는 10,000*500이고 시작점은 최대 10,000개이다. 그러면 최대 10000번의 시작점에 3가지 방향으로 맵을 최대 500의 깊이까지 탐색하기 때문에 최악의 경우 10,000*3^500으로 시간초과가 발생하게 된다. 그래서 그리디 알고리즘을 사용하여 최적의 기법을 통해 답을 구해줘야 한다. 탐욕적으로 해결하는 방법은 다음과 같이 떠올릴 수 있다.
탐욕적인 선택 방식
(0행, 0열)을 첫 번째 출발점이라고 하고 그 밑으로 차례대로 (0,1)을 두 번째, (0,2)을 세 번째 ... 라고 하자. 마찬가지로 (c-1행, 0열)을 첫 번째 도착점이라고 하고 그 밑으로 차례대로, (c-1, 1)을 두 번째, (c-1, 2)을 세 번째 ... 라고 하자. 그림으로 나타내면 다음과 같다.
그러면 어떻게 탐색을 해줘야 그리디하게 최적해를 찾을 수 있을까? i를 출발점의 열, j를 도착점의 열이라고 했을 때 i를 0부터 r-1 오름차순으로 탐색한다면 도착점또한 0부터 r-1순대로 도착하게끔 탐색을 해주면 된다. 따라서 출발점을 0부터 오름차순으로 탐색을 하고 탐색 방향 순서는 (-1, 0, 1)로 해주는 것이다.
정리하면 탐욕적인 선택 방식은 0~r-1순대로 출발점을 탐색하여 0~r-1순으로 가장 작은 도착점부터 연결하도록 탐색을 하는 것이다. 그림으로 보면 다음과 같다. (0,0)- (C-1,0), (0,1)-(C-1,1), (0,2)-(C-1,2)로 총 3개의 파이프를 연결되는 것을 볼 수 있다.
경로 재탐색하지 않기
실을 연결하는 것이라고 생각해보면 최대한 실을 엉키지 않게 하는 방법으로 탐색을 한다고 보면 된다. 그리고 이렇게 그리디하게 탐색을 하면 이전에 탐색을 실패했든, 성공했든 해당 경로는 더 이상 탐색이 불가능한 지역으로 설정하여 탐색 연산을 줄여줄 수 있다. 이유는 다음과 같다.
- 파이프 연결을 성공했을 경우
-
- 현재까지 탐색한 경로로 연결이 되었기 때문에 새로운 파이프 설치가 불가능하다.
- 파이프 연결을 실패했을 경우
- 현재 경로에는 최적의 경로가 존재하지 않기 때문에 파이프 설치가 불가능하다.
- ex) (0,0)에서 (C-1,0)의 연결을 실패했는데 (0,1)에서 (C-1,0)을 설치할 수 있는 경로가 존재할 수 없다.
설계
- 0부터 r-1까지 오름차순으로 출발점을 지정하여 DFS탐색을 해준다.
- 그리디하게 탐색 방향 순서는 (-1, 0, 1)로 설정한다.
- C-1행에 도착하였다면 파이프 연결을 성공한 것이므로 cnt++을 해준다.
- 만약 실패한 경로일 경우 false를 리턴하여 해당 깊이 탐색을 종료한다.
반대로 출발점을 r-1부터 0으로 내림차순으로 탐색하고 싶다면 탐색 방향 순서를 (1, 0, -1)로 바꿔주면 된다.
풀이 코드
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
static int r,c,cnt=0;
static char[][] map;
static boolean[][] check;
static int[] dr= {-1,0,1}; // 출발점 0~r-1이므로, 도착점 0~r-1순대로 도착하게 탐색 방향 설정
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
r = Integer.parseInt(st.nextToken());
c = Integer.parseInt(st.nextToken());
map =new char[r][c];
for(int i=0; i<r; i++) {
String line = br.readLine();
for(int j=0; j<c; j++) {
map[i][j] = line.charAt(j);
}
}
// 0~r-1순으로 출발점 설정
for(int i=0; i<r; i++) {
dfs(0,i);
}
System.out.println(cnt);
}
static boolean dfs(int x, int y) {
for(int i=0; i<3; i++) {
int nx = x + 1;
int ny = y + dr[i];
if(nx<0 || nx>c-1 || ny<0 || ny>r-1) continue;
if(map[ny][nx] =='.') {
if(nx == c-1) {
cnt ++;
return true;
}
map[ny][nx] = '-';
if(dfs(nx,ny)) return true;
}
}
return false;
}
}
'Dot Algo∙ DS > PS' 카테고리의 다른 글
[BOJ] 백준 1781번 컵라면 (Java) (0) | 2021.10.28 |
---|---|
[BOJ] 백준 2012번 등수 매기기 (Java) (0) | 2021.10.27 |
[BOJ] 백준 1946번 신입 사원 (Java) (0) | 2021.10.25 |
[BOJ] 백준 17073번 나무 위의 빗물 (Java) (0) | 2021.10.24 |
[BOJ] 백준 14675번 단절점과 단절선 (Java) (0) | 2021.10.23 |