17070번 - 파이프 옮기기 1
문제 풀이 시간 : 47
문제 요약
- 크기
N × N격자에서 파이프를 이동시키는 경우의 수를 구하는 문제 - 파이프는 항상 2칸을 차지하며, 방향은 총 3가지
- 시작 상태는
(1,1) ~ (1,2)가로 방향 - 파이프의 한쪽 끝이 (N, N) 에 도달하는 모든 경우의 수를 계산
문제 풀이
처음 문제를 읽었을 때는
👉 그래프 탐색 문제처럼 보였고, BFS로 접근하면 되겠다고 생각했다.
상태는 다음 3가지 정보로 정의할 수 있었다.
- 현재 파이프 끝 좌표
(x, y) - 파이프 방향
즉, (x, y, dir) 을 하나의 상태로 보고
큐를 이용해 가능한 모든 이동을 탐색하는 방식이었다.
📌 초기 접근 – BFS 완전 탐색
핵심 아이디어
- 현재 방향에 따라 이동 가능한 다음 방향이 정해짐
- 이동 시 필요한 칸들이 모두
0인지 확인 (N, N)에 도달하면 경우의 수 증가
초기 코드
import java.io.*;
import java.util.*;
public class Main {
public static int[][] map;
public static int n;
// 현재 위치 (x, y), 현재 방향 dir 에서
// next 방향으로 이동이 가능한지 검사하는 함수
public static boolean check(int x, int y, int dir, int next) {
// 현재 가로 방향
if (dir == 1) {
// 가로 -> 가로 (오른쪽)
if (next == 1 && map[x][y + 1] != 1) {
return true;
}
// 가로 -> 대각선
if (next == 3 &&
map[x][y + 1] != 1 &&
map[x + 1][y] != 1 &&
map[x + 1][y + 1] != 1) {
return true;
}
}
// 현재 세로 방향
else if (dir == 2) {
// 세로 -> 세로 (아래)
if (next == 2 && map[x + 1][y] != 1) {
return true;
}
// 세로 -> 대각선
if (next == 3 &&
map[x][y + 1] != 1 &&
map[x + 1][y] != 1 &&
map[x + 1][y + 1] != 1) {
return true;
}
}
// 현재 대각선 방향
else if (dir == 3) {
// 대각선 -> 가로
if (next == 1 && map[x][y + 1] != 1) {
return true;
}
// 대각선 -> 세로
if (next == 2 && map[x + 1][y] != 1) {
return true;
}
// 대각선 -> 대각선
if (next == 3 &&
map[x][y + 1] != 1 &&
map[x + 1][y] != 1 &&
map[x + 1][y + 1] != 1) {
return true;
}
}
return false;
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
n = Integer.parseInt(br.readLine());
// 외곽을 벽으로 처리하기 위해 여유 공간을 둠
map = new int[n + 2][n + 2];
StringTokenizer st;
for (int i = 1; i <= n; i++) {
st = new StringTokenizer(br.readLine());
for (int j = 1; j <= n; j++) {
map[i][j] = st.nextToken().charAt(0) - '0';
}
}
// 경계 처리
for (int i = 0; i <= n + 1; i++) {
map[i][0] = map[i][n + 1] = 1;
map[0][i] = map[n + 1][i] = 1;
}
Queue<int[]> q = new ArrayDeque<>();
// 시작 상태: (1,2), 가로 방향
q.add(new int[]{1, 2, 1});
int ans = 0;
while (!q.isEmpty()) {
int[] now = q.poll();
int x = now[0];
int y = now[1];
int dir = now[2];
// 도착 지점
if (x == n && y == n) {
ans++;
continue;
}
// 방향에 따라 가능한 이동
switch (dir) {
case 1: // 가로
if (check(x, y, 1, 1))
q.add(new int[]{x, y + 1, 1});
if (check(x, y, 1, 3))
q.add(new int[]{x + 1, y + 1, 3});
break;
case 2: // 세로
if (check(x, y, 2, 2))
q.add(new int[]{x + 1, y, 2});
if (check(x, y, 2, 3))
q.add(new int[]{x + 1, y + 1, 3});
break;
case 3: // 대각선
if (check(x, y, 3, 1))
q.add(new int[]{x, y + 1, 1});
if (check(x, y, 3, 2))
q.add(new int[]{x + 1, y, 2});
if (check(x, y, 3, 3))
q.add(new int[]{x + 1, y + 1, 3});
break;
}
}
System.out.println(ans);
}
}
❌ BFS 접근의 문제점
- 같은 상태를 여러 번 탐색
- 경우의 수가 기하급수적으로 증가
N이 작아도 메모리 / 시간 비효율
👉 실제로는 통과는 하지만,
불필요한 상태 탐색이 너무 많다는 점이 문제였다.

🔥 개선 아이디어 – DP로 상태 누적하기
문제를 다시 보니,
👉 이 문제는 전형적인 DP 문제였다.
(i, j)위치에- 어떤 방향의 파이프가 도착하는 경우의 수만 알면 된다
- 그 이전 경로는 중요하지 않다
💡 DP 정의
dp[dir][i][j](i, j)에 파이프 끝이 위치하고dir방향으로 놓여 있는 경우의 수
방향 인덱스:
0: 가로1: 세로2: 대각선
🔄 점화식 개념
- 가로 → 가로, 대각선
- 세로 → 세로, 대각선
- 대각선 → 가로, 세로, 대각선
👉 이동 가능 여부만 체크해서 누적
⏱️ 시간 복잡도
N ≤ 16- 모든 칸 × 3방향
👉 O(N²)
BFS보다 훨씬 안정적이고 빠르다.
최종 코드
import java.io.*;
import java.util.*;
public class Main {
// 집 상태를 저장할 지도
// 0 : 빈 칸, 1 : 벽
public static int[][] map;
public static int n;
/**
* 현재 파이프 끝 좌표 (x, y),
* 현재 방향 dir 에서
* next 방향으로 이동이 가능한지 확인하는 함수
*
* dir / next
* 1 : 가로
* 2 : 세로
* 3 : 대각선
*/
public static boolean check(int x, int y, int dir, int next) {
// 현재 파이프가 가로 방향일 때
if (dir == 1) {
// 가로 -> 가로 (오른쪽으로 한 칸)
// (x, y+1) 칸만 비어 있으면 됨
if (next == 1 && map[x][y + 1] != 1) {
return true;
}
// 가로 -> 대각선
// 오른쪽, 아래, 오른쪽 아래 총 3칸이 비어 있어야 함
if (next == 3 &&
map[x][y + 1] != 1 &&
map[x + 1][y] != 1 &&
map[x + 1][y + 1] != 1) {
return true;
}
}
// 현재 파이프가 세로 방향일 때
else if (dir == 2) {
// 세로 -> 세로 (아래로 한 칸)
// (x+1, y) 칸만 확인
if (next == 2 && map[x + 1][y] != 1) {
return true;
}
// 세로 -> 대각선
// 오른쪽, 아래, 오른쪽 아래 총 3칸 확인
if (next == 3 &&
map[x][y + 1] != 1 &&
map[x + 1][y] != 1 &&
map[x + 1][y + 1] != 1) {
return true;
}
}
// 현재 파이프가 대각선 방향일 때
else if (dir == 3) {
// 대각선 -> 가로
// 오른쪽 칸만 비어 있으면 됨
if (next == 1 && map[x][y + 1] != 1) {
return true;
}
// 대각선 -> 세로
// 아래 칸만 비어 있으면 됨
if (next == 2 && map[x + 1][y] != 1) {
return true;
}
// 대각선 -> 대각선
// 오른쪽, 아래, 오른쪽 아래 모두 확인
if (next == 3 &&
map[x][y + 1] != 1 &&
map[x + 1][y] != 1 &&
map[x + 1][y + 1] != 1) {
return true;
}
}
// 이동 불가능한 경우
return false;
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
n = Integer.parseInt(br.readLine());
// 경계 처리를 쉽게 하기 위해 n+2 크기로 생성
map = new int[n + 2][n + 2];
StringTokenizer st;
// 지도 입력
for (int i = 1; i <= n; i++) {
st = new StringTokenizer(br.readLine());
for (int j = 1; j <= n; j++) {
map[i][j] = st.nextToken().charAt(0) - '0';
}
}
// 외곽을 전부 벽(1)으로 처리
// 범위 체크를 하지 않아도 되도록 하기 위함
for (int i = 0; i <= n + 1; i++) {
map[i][0] = 1;
map[i][n + 1] = 1;
map[0][i] = 1;
map[n + 1][i] = 1;
}
/**
* dp[dir][i][j]
* (i, j)에 파이프 끝이 위치하고
* dir 방향으로 놓여 있는 경우의 수
*
* dir
* 0 : 가로
* 1 : 세로
* 2 : 대각선
*/
int[][][] dp = new int[3][n + 2][n + 2];
// 시작 상태
// (1,1)~(1,2) 가로 방향이므로
dp[0][1][2] = 1;
// 모든 칸을 순회하면서 DP 누적
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
// 현재 가로 방향으로 도착한 경우
if (dp[0][i][j] > 0) {
// 가로 -> 가로
if (check(i, j, 1, 1))
dp[0][i][j + 1] += dp[0][i][j];
// 가로 -> 대각선
if (check(i, j, 1, 3))
dp[2][i + 1][j + 1] += dp[0][i][j];
}
// 현재 세로 방향으로 도착한 경우
if (dp[1][i][j] > 0) {
// 세로 -> 세로
if (check(i, j, 2, 2))
dp[1][i + 1][j] += dp[1][i][j];
// 세로 -> 대각선
if (check(i, j, 2, 3))
dp[2][i + 1][j + 1] += dp[1][i][j];
}
// 현재 대각선 방향으로 도착한 경우
if (dp[2][i][j] > 0) {
// 대각선 -> 가로
if (check(i, j, 3, 1))
dp[0][i][j + 1] += dp[2][i][j];
// 대각선 -> 세로
if (check(i, j, 3, 2))
dp[1][i + 1][j] += dp[2][i][j];
// 대각선 -> 대각선
if (check(i, j, 3, 3))
dp[2][i + 1][j + 1] += dp[2][i][j];
}
}
}
// (n, n)에 도달하는 모든 방향의 경우의 수 합
System.out.println(
dp[0][n][n] + dp[1][n][n] + dp[2][n][n]
);
}
}
함께 읽으면 좋은 글
5430번 - AC
함수 R은 배열을 뒤집는 연산 함수 D는 첫 번째 원소를 버리는 연산 단, 배열이 비어 있을 때 D를 수행하면 error 주어진 함수 문자열을 순서대로 수행한 뒤 최종 배열을 출력하는 문제 처음에는 문제 설명 그대로 R이 나올 때마다 배열을 실제로 뒤집고, D가 나오면 맨 앞…
1966번 - 프린터 큐
여러 문서가 큐에 들어 있고, 각 문서마다 중요도가 있음 맨 앞 문서를 확인했을 때, 더 높은 중요도의 문서가 뒤에 하나라도 있으면 맨 뒤로 보냄 그렇지 않으면 바로 인쇄 특정 위치의 문서가 몇 번째로 인쇄되는지 구하는 문제 이 문제는 프린터의 동작을 그대로 구현하면 되는…
1158 - 요세푸스 문제
1번부터 N번까지 사람이 원형으로 앉아 있음 순서대로 K번째 사람을 제거 모든 사람이 제거될 때까지 반복 제거되는 순서를 출력하는 문제 이 문제는 원형으로 순회하면서 K번째를 제거하는 과정을 그대로 구현하면 된다.
2164번 - 카드2
1부터 N까지 카드가 순서대로 쌓여 있음 맨 위 카드를 버리고, 그다음 맨 위 카드를 맨 아래로 옮기는 과정을 반복 마지막에 남는 카드 번호를 구하는 문제 이 문제는 규칙을 찾기보다, 문제에서 하라는 과정을 그대로 구현하면 된다. 카드 더미에서 반복되는 동작은 두 가지다.