백준 17070 – 파이프 옮기기 1

Python을 이용한 17070번 : 파이프 옮기기 1 에 대해서 포스팅하겠습니다. 17070번은 다이나믹 프로그래밍, 그래프 이론, 그래프 탐색의 알고리즘으로 분류되는 문제입니다.

14503_표지

17070번 문제설명

  • 조건
    • 가장 처음에 파이프는 (1, 1)와 (1, 2)를 차지하고 있고, 방향은 가로이다.
    • 파이프를 밀 수 있는 방향은 총 3가지가 있으며, →, ↘, ↓ 방향이다. 파이프는 밀면서 회전시킬 수 있다. 회전은 45도만 회전시킬 수 있으며, 미는 방향은 오른쪽, 아래, 또는 오른쪽 아래 대각선 방향이어야 한다.
  • 입력
    • 첫째 줄에 집의 크기 N(3 ≤ N ≤ 16)이 주어진다. 둘째 줄부터 N개의 줄에는 집의 상태가 주어진다. 빈 칸은 0, 벽은 1로 주어진다. (1, 1)과 (1, 2)는 항상 빈 칸이다.
  • 출력
    • 첫째 줄에 파이프의 한쪽 끝을 (N, N)으로 이동시키는 방법의 수를 출력한다. 이동시킬 수 없는 경우에는 0을 출력한다. 방법의 수는 항상 1,000,000보다 작거나 같다.

17070번 접근방법

1. 17070번의 DFS 탐색

  1. 시작 지점과 방향 설정: 처음에 파이프는 (1, 1)과 (1, 2)를 차지하며, 방향은 가로입니다. 따라서 DFS 탐색은 (0, 1) 위치에서 가로 방향으로 시작합니다.
  2. 경로 탐색:
    • 가로 방향(rotation = 0)인 경우, 파이프는 오른쪽 또는 대각선 아래로 이동할 수 있습니다.
    • 세로 방향(rotation = 1)인 경우, 파이프는 아래쪽 또는 대각선 아래로 이동할 수 있습니다.
    • 대각선 방향(rotation = 2)인 경우, 파이프는 오른쪽, 아래쪽 또는 대각선 아래로 이동할 수 있습니다.
  3. 이동 조건 확인:
    • 범위 확인: 파이프의 이동이 격자판의 범위를 벗어나지 않는지 확인합니다.
    • 벽 확인: 이동하려는 칸이 벽이 아닌지, 특히 대각선 이동 시 인접한 칸이 벽인지 확인합니다.
  4. 목표 지점 도달 여부 확인: 파이프 한쪽 끝이 (N, N) 위치에 도달했는지 확인합니다. 도달한 경우, 경로 수를 증가시킵니다.
  5. 재귀적 탐색: 가능한 모든 방향으로 파이프를 이동시켜 재귀적으로 탐색을 계속합니다.

2. 17070번의 DP 점화식

2-1. DP 점화식 도출 과정:

  • 상태 정의: DP[r][c][d]를 (r, c) 위치에서 파이프가 d 방향(0: 가로, 1: 세로, 2: 대각선)으로 놓일 수 있는 배치 방법의 수라고 정의합니다.
  • 초기 상태: (0, 1) 위치에서 파이프는 가로 방향으로만 놓일 수 있으므로, DP[0][1][0] = 1로 초기화합니다. 다른 상태는 0으로 초기화합니다.
  • 전이(점화식):
    • 가로 방향(d=0)으로 놓인 파이프를 (r, c+1)로 이동시키기 위해서는 이전 상태가 가로 또는 대각선이어야 합니다.
      • DP[r][c+1][0] += DP[r][c][0] + DP[r][c][2]
    • 세로 방향(d=1)으로 놓인 파이프를 (r+1, c)로 이동시키기 위해서는 이전 상태가 세로 또는 대각선이어야 합니다.
      • DP[r+1][c][1] += DP[r][c][1] + DP[r][c][2]
    • 대각선 방향(d=2)으로 놓인 파이프를 (r+1, c+1)로 이동시키기 위해서는 이전 상태가 가로, 세로, 또는 대각선이어야 합니다.
      • DP[r+1][c+1][2] += DP[r][c][0] + DP[r][c][1] + DP[r][c][2]
  • 벽 확인: 이동하려는 위치에 벽이 있으면 이동할 수 없습니다. 따라서 해당 위치가 모두 빈 칸인지 확인해야 합니다.

2-2. 점화식 도출을 위한 생각 과정:

  • 경우의 수 분할: 파이프가 각 방향으로 이동할 때 고려해야 할 이전 상태를 분리해서 생각합니다.
  • 경계 조건: 파이프가 격자판 밖으로 나가거나 벽을 만나는 경우를 배제합니다.
  • 중복 계산 방지: 각 위치와 방향별로 이동 가능한 경로의 수를 계산함으로써 중복 계산을 방지합니다.

2-3. 점화식의 의미:

  • DP[r][c][0]: (r, c)에 가로 방향으로 파이프가 놓일 수 있는 모든 경우의 수.
  • DP[r][c][1]: (r, c)에 세로 방향으로 파이프가 놓일 수 있는 모든 경우의 수.
  • DP[r][c][2]: (r, c)에 대각선 방향으로 파이프가 놓일 수 있는 모든 경우의 수.

3. 17070번 코드

Python
#https://www.acmicpc.net/problem/17070
from sys import stdin

# N: 격자판의 크기 (N x N)
n = int(stdin.readline())
# maps: 격자판의 상태를 나타내는 2차원 리스트. 0은 빈 칸, 1은 벽
maps = [list(map(int, stdin.readline().split())) for i in range(n)]
# path: 파이프가 (N, N) 위치로 이동할 수 있는 방법의 총 수
path = 0

# dfs 함수: 파이프의 현재 위치와 상태를 기준으로 이동 가능한 경로를 탐색하는 함수
def dfs(i, j, rotation):
    global path, maps
    # 이동이 격자판 범위를 벗어나거나, 벽(1)을 만나면 탐색 중단
    if not(0 <= i < n) or not(0 <= j < n) or maps[i][j] == 1:
        return 
    # 대각선 이동 중 벽이 있으면 이동 불가, 탐색 중단
    if rotation == 2 and (maps[i-1][j] == 1 or maps[i][j-1] == 1):
        return
    # (N, N) 위치에 도달하면 경로 수 증가 후 탐색 중단
    if i == n-1 and j == n-1:
        path += 1
        return
    
    # 파이프가 가로 방향일 때: 오른쪽(가로) 또는 대각선으로 이동 가능
    if rotation == 0:
        dfs(i, j+1, 0) # 오른쪽으로 가로 이동
        dfs(i+1, j+1, 2) # 대각선으로 이동
    
    # 파이프가 세로 방향일 때: 아래(세로) 또는 대각선으로 이동 가능
    if rotation == 1:
        dfs(i+1, j, 1) # 아래로 세로 이동
        dfs(i+1, j+1, 2) # 대각선으로 이동
    
    # 파이프가 대각선 방향일 때: 모든 방향으로 이동 가능 (가로, 세로, 대각선)
    if rotation == 2:
        dfs(i, j+1, 0) # 오른쪽으로 가로 이동
        dfs(i+1, j, 1) # 아래로 세로 이동
        dfs(i+1, j+1, 2) # 대각선으로 이동

# N이 16이 아닌 경우에 대한 예외 처리입니다.
if n != 16:
    # 특정 조건에서는 경로가 존재하지 않으므로 0을 출력합니다.
    if (maps[0][2]==1 or (maps[n-1][n-2] == 1 and maps[n-2][n-1] == 1)):
        print(0)
    else:
        # DFS 탐색 시작점: 처음 파이프 위치(0, 1)와 방향(가로)
        dfs(0, 1, 0)
        # 최종 계산된 이동 경로의 수 출력
        print(path)
else:
    # N이 16인 경우, 동적 프로그래밍을 사용하여 문제를 해결합니다.
    dp = [[[0] * 3 for _ in range(n)] for _ in range(n)]
    dp[0][1][0] = 1  # 초기 조건 설정

    for r in range(n):
        for c in range(n):
            # 가로로 이동하는 경우의 수를 계산합니다.
            if c + 1 < n and maps[r][c + 1] == 0:
                dp[r][c + 1][0] += dp[r][c][0] + dp[r][c][2]
            
            # 세로로 이동하는 경우의 수를 계산합니다.
            if r + 1 < n and maps[r + 1][c] == 0:
                dp[r + 1][c][1] += dp[r][c][1] + dp[r][c][2]

            # 대각선으로 이동하는 경우의 수를 계산합니다.
            if r + 1 < n and c + 1 < n and maps[r + 1][c] == 0 and maps[r][c + 1] == 0 and maps[r + 1][c + 1] == 0:
                dp[r + 1][c + 1][2] += dp[r][c][0] + dp[r][c][1] + dp[r][c][2]

    # 최종적으로 (N, N)에 도달하는 모든 경우의 수를 합하여 출력합니다.
    print(sum(dp[n - 1][n - 1]))

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

Scroll to Top