[백준/골드5] 4811번 알약 | DP | 파이썬 Python

문제

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

70세 박종수 할아버지는 매일매일 약 반알을 먹는다. 손녀 선영이는 종수 할아버지에게 약이 N개 담긴 병을 선물로 주었다.

첫째 날에 종수는 병에서 약 하나를 꺼낸다. 그 다음, 그 약을 반으로 쪼개서 한 조각은 먹고, 다른 조각은 다시 병에 넣는다.

다음 날부터 종수는 병에서 약을 하나 꺼낸다. (약은 한 조각 전체 일 수도 있고, 쪼갠 반 조각 일 수도 있다) 반 조각이라면 그 약을 먹고, 아니라면 반을 쪼개서 한 조각을 먹고, 다른 조각은 다시 병에 넣는다.

종수는 손녀에게 한 조각을 꺼낸 날에는 W를, 반 조각을 꺼낸 날에는 H 보낸다. 손녀는 할아버지에게 받은 문자를 종이에 기록해 놓는다. 총 2N일이 지나면 길이가 2N인 문자열이 만들어지게 된다. 이때, 가능한 서로 다른 문자열의 개수는 총 몇 개일까?

 

입력

입력은 최대 1000개의 테스트 케이스로 이루어져 있다. 각 테스트 케이스는 한 줄이며, 병에 들어있는 약의 개수 N ≤ 30 가 주어진다.

입력의 마지막 줄에는 0이 하나 주어진다.

 

출력

각 테스트 케이스에 대해서 가능한 문자열의 개수를 출력한다.

 

예제

 

나의 풀이 (실패)

import sys
input = sys.stdin.readline

pills  = []

while True:
    N = int(input())

    if N == 0:
        break
    else:
        pills.append(N)

result = [0] * (max(pills) + 1)

def texting(n, str, w, h):
    if len(str) == n*2:
        result[n] += 1
        return

    if w < n:
        texting(n, str + "W", w+1, h)
    if h < n and h < w:
        texting(n, str + "H", w, h+1)

for n in pills:
    texting(n, "W", 1, 0)
    print(result[n])
  • 재귀함수를 이용해서 `n`개의 약마다 서로 다른 문자열의 개수를 각각 구했는데 `n`이 커질수록 비효율적인 구조라 시간초과가 발생한다.
  • 한 번에 `30`까지 계산하면서 `n`개마다 갯수를 추가하는 방법도 시도해 봤지만 `w`와 `h`의 짝을 맞추는 게 쉽지 않았다.

 

다른 사람의 풀이

import sys
input = sys.stdin.readline

# 약의 최대 개수만큼 2차원 리스트 생성
dp = [[0] * 31 for _ in range(31)]

# h만 남은 경우에는 경우의 수가 하나밖에 없다
for j in range(1, 31):
    dp[0][j] = 1

for i in range(1, 31):
    for j in range(30):
        # h가 하나도 남지 않았다면 무조건 w를 먹어야 한다
        if j == 0:
            dp[i][j] = dp[i-1][j+1]
        # w를 먹거나, h를 먹거나 두 가지 경우의 수를 더한다
        else:
            dp[i][j] = dp[i-1][j+1] + dp[i][j-1]

while True:
    n = int(input())
    
    if n == 0:
        break
    else:
        # n개의 w가 있을 때 경우의 수 출력
        print(dp[n][0])
  • 약의 최대 길이만큼 2차원 리스트를 생성하는데, 이때 `dp[i][j]`는 완전한 알약이 `i`개, 반 개의 알약이 `j`개 남아있을 때 만들 수 있는 경우의 수를 나타낸다.
  • 약이 H만 남은 경우에는 `HHHH...`와 같이 H를 일렬로 나열하는 경우의 수밖에 없기 때문에 `dp[0][j]`를 모두 `1`로 초기화한다.
  • `(i, j)` 즉 완전한 알약이 `i`개, 반 개의 알약이 `j`개 남아있을 때 할 수 있는 선택은 두 가지다.
    • W 선택: 완전한 알약을 하나 꺼내서 반 개로 쪼갠다 -> `(i-1, j+1)`
    • H 선택: 반 조각을 하나 먹는다 -> `(i, j-1)`
    • 따라서 `(i, j)` 상태에서 만들 수 있는 경우의 수는 W를 선택한 경우의 수 + H를 선택한 경우의 수가 된다.
  • `dp`의 모든 요소를 탐색하면서 이전 값을 이용해 현재의 값을 계산한다.
    • 만약 H가 하나도 남지 않았다면 무조건 W를 먹는 선택을 내려야 한다. -> `dp[i][j] = dp[i-1][j+1]`
    • 그 외의 경우에는 W를 먹은 경우의 수와 H를 먹은 경우의 수를 더한다. -> `dp[i][j] = dp[i-1][j+1] + dp[i][j-1]`
  • 최종적으로 입력받은 약의 개수에 따라 N개의 완전한 알약이 있을 때 경우의 수, 즉 `dp[n][0]`을 출력한다.

 

회고

    • 풀이를 봐도 이해가 안 돼서 오래 붙잡고 있었던 문제다. 지금도 완벽하게 이해가 되는 건 아니지만 어느 정도 접근 방식과 흐름은 이해가 된다.
    • 나에게 절망을 안겨준 문제였지만, 이런 어려운 문제를 풀어봐야 성장할 수 있는 거겠지.
    • 나중에 다시 한 번 풀어보고 싶어서 백준에 풀이는 제출하지 않고 [시도했지만 맞지 못한 문제]에 남겨두기로 했다.

 

참고