문제
https://www.acmicpc.net/problem/20152
1. pc방으로 상하좌우 이동, 한 번에 1칸 이동
2. 최단경로로 움직임
3. y>x인 곳은 침수됨
4. 침수된 곳을 피해서 pc방까지 갈 수 있는 경로의 개수
5. pc방 좌표 == 집 좌표 -> 경로 1가지
입력:
(H, H) 집 좌표 (N, N) pc방 좌표
풀이1
bfs, dfs - 시간초과
처음에는 방문 가능한 곳의 좌표를 리스트에 넣어서 리스트의 조합으로 풀려고 했다.
그러나 이 방법은 최악의 경우 30*30행렬에서 좌표의 모든 조합을 구해야 하므로 연산 횟수가 1억이 넘는다.
다음으로 행렬을 완전탐색하는 경우를 생각해보았다.
완전탐색은 일반적으로 O(2^N) O(N!)의 시간복잡도를 가지고 있다.
이 경우도 연산 횟수가 1억이 넘는다.
그러나 bfs, dfs를 사용하였고, 역시나 시간초과가 발생했다.
경로의 개수만 구하면 되므로, 시작점과 끝점을 (N,N) (H,H) 중 뭘로 하든 상관 없다.
1. |N-H|*|N-H|크기의 행렬을 만듦
2. (x>y)인 경우 각 좌표에 현재까지 최단거리를 저장 -> bfs:한 번만 탐색
3. '1~도착지점에 저장된 거리'까지 차례로 1씩 증가하는 경로가 몇 개 있는지 세기 -> dfs
테스트 케이스는 맞았지만 시간 초과가 발생하였다.
코드1
import java.io.*;
import java.util.*;
public class boj_20152 {
static int N, H;
static int[][] board;
static boolean[][] check;
static int ans;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
StringTokenizer st;
st = new StringTokenizer(br.readLine());
H = Integer.parseInt(st.nextToken());
N = Integer.parseInt(st.nextToken());
int size = Math.abs(N-H)+1;
board = new int[size][size];
check = new boolean[size][size];
bfs(0, 0); //현재까지 최단거리 저장
dfs(0, 0, 0); //차례로 1씩 증가하는 경로의 개수 탐색
bw.write(ans+" ");
bw.close();
}
static void bfs(int x, int y){
Queue<Point> q = new LinkedList<>();
int[] dx = {0, 0, 1, -1};
int[] dy = {1, -1, 0, 0};
q.offer(new Point(x, y));
check[x][y] = true;
while(!q.isEmpty()){
Point p = q.poll();
for(int i=0; i<4; i++){
int nx = p.x + dx[i];
int ny = p.y + dy[i];
if(nx<0 || nx>=board.length || ny<0 || ny>=board.length)
continue;
if(check[nx][ny] || ny > nx)
continue;
board[nx][ny] = board[p.x][p.y] + 1;
q.offer(new Point(nx, ny));
check[nx][ny] = true;
}
}
}
static void dfs(int num, int x, int y){
if(num == board[board.length-1][board.length-1]){
ans++;
return;
}
//오른쪽, 아래만 탐색 (0, 1) (1, 0)
//num과 같은 숫자가 있다면 재귀, 그 좌표와 num+1을 인자로 넘겨줌
num += 1;
if(y+1<0 || y+1>=board.length)
return;
if(board[x][y+1] == num)
dfs(num, x, y+1);
if(x+1<0 || x+1>=board.length)
return;
if(board[x+1][y] == num)
dfs(num, x+1, y);
}
}
class Point{
int x, y;
public Point(int x, int y){
this.x = x;
this.y = y;
}
}
풀이2
dp
1. 각 좌표에 도달 가능한 경우의 수 저장하기
2. pc방 좌표 == 집 좌표 -> 경로 1가지 이므로 board[0][0] = 1
3. 0열의 모든 좌표는 도달 가능한 경우의 수가 1가지 뿐이므로 모두 1로 채운다.
4. 최단거리로 진행하므로 현재 좌표에서 오른쪽, 아래 두 가지 방향으로만 간다.
5. 따라서 현재 좌표에 도달 가능한 경우의 수는 왼쪽, 위에서 온 경우 뿐이므로 두 위치에 있는 수의 합이 된다.
-> board[x][y] = board[x-1][y] + board[x][y-1]
6. 주의할 점은 열>행인 좌표는 지나갈 수 없으므로, 0의 값이 들어가있어야 한다.
-> board 선언 시 모두 0으로 초기화되어있어서 따로 넣어주지 않았다.
-> 열>행인 경우를 피해서 값을 저장하기 위해 이중 for문의 두 번째 루프에 조건을 주었다.
7. N, H값이 커지면 경우의 수가 4byte(-2^31~2^31)보다 커지므로 long타입을 사용해야 한다.
코드2
import java.io.*;
import java.util.*;
public class Main {
static int N, H;
static long[][] board;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
StringTokenizer st;
st = new StringTokenizer(br.readLine());
H = Integer.parseInt(st.nextToken());
N = Integer.parseInt(st.nextToken());
int size = Math.abs(N-H)+1;
board = new long[size][size];
board[0][0] = 1;
for(int i=1; i<size; i++){
board[i][0] = 1;
for(int j=1; j<i+1; j++){
board[i][j] = board[i-1][j] + board[i][j-1];
}
}
bw.write(board[size-1][size-1] + " ");
bw.close();
}
}
Note
- bfs로 한 번만 행렬을 탐색하는 것을 dp로 착각하였다.
- dp는 기저값을 넣고 그 값을 이용해서 점화식 규칙대로 다음 값을 구하는 알고리즘이다.
- 어떻게 bfs를 구현해놓고 dp로 풀었다고 착각할 수가 있는지 황당하다..
- dp에는 현재까지의 '이동 길이'가 아닌 현재 좌표로 도달할 수 있는 '경로의 수'를 저장하는 것이다.
- 이 부분을 착각하고 있어서 이해하는 데 시간이 오래 걸렸다.
- 이전에 비슷한 문제를 푼 적이 있었는데, 이 문제로 다시 한 번 복기되었다.
'BOJ' 카테고리의 다른 글
[백준 2473번 / java] 세 용액 (0) | 2024.07.10 |
---|---|
[백준 14890번 / java] 경사로 (0) | 2024.07.10 |
[백준 16918번 / java] 봄버맨 (0) | 2024.07.09 |
[백준 1263번 / java] 시간 관리 (0) | 2024.07.09 |
[백준 20922번 / java] 겹치는 건 싫어 (0) | 2024.07.09 |