Note
메모이제이션을 활용하여 재귀 함수를 최적화하는 문제입니다.
문제
재귀 호출만 생각하면 신이 난다! 아닌가요?
다음과 같은 재귀함수 w(a, b, c)가 있다.
if a <= 0 or b <= 0 or c <= 0, then w(a, b, c) returns:
1
if a > 20 or b > 20 or c > 20, then w(a, b, c) returns:
w(20, 20, 20)
if a < b and b < c, then w(a, b, c) returns:
w(a, b, c-1) + w(a, b-1, c-1) - w(a, b-1, c)
otherwise it returns:
w(a-1, b, c) + w(a-1, b-1, c) + w(a-1, b, c-1) - w(a-1, b-1, c-1)
위의 함수를 구현하는 것은 매우 쉽다. 하지만, 그대로 구현하면 값을 구하는데 매우 오랜 시간이 걸린다. (예를 들면, a=15, b=15, c=15)
a, b, c가 주어졌을 때, w(a, b, c)를 출력하는 프로그램을 작성하시오.
문제 링크: https://www.acmicpc.net/problem/9184
입력
입력은 세 정수 a, b, c로 이루어져 있으며, 한 줄에 하나씩 주어진다. 입력의 마지막은 -1 -1 -1로 나타내며, 세 정수가 모두 -1인 경우는 입력의 마지막을 제외하면 없다.
출력
입력으로 주어진 각각의 a, b, c에 대해서, w(a, b, c)를 출력한다.
제한
-50 ≤ a, b, c ≤ 50
예제
예제 입력 1:
1 1 1
2 2 2
10 4 6
50 50 50
-1 7 18
-1 -1 -1
예제 출력 1:
w(1, 1, 1) = 2
w(2, 2, 2) = 4
w(10, 4, 6) = 523
w(50, 50, 50) = 1048576
w(-1, 7, 18) = 1
문제 분석
이 문제는 메모이제이션을 사용하지 않으면 시간 초과가 발생하는 전형적인 문제입니다.
핵심 이해
- 재귀 함수: 복잡한 조건에 따라 재귀적으로 계산
- 중복 계산: 같은 (a, b, c) 조합이 여러 번 계산됨
- 최적화 필요: 메모이제이션을 사용하여 중복 계산 제거
왜 느린가?
메모이제이션 없이 w(15, 15, 15)를 계산하면:
- 같은 (a, b, c) 조합이 수백만 번 중복 계산됨
- 시간 복잡도가 지수적으로 증가
메모이제이션 사용 시
- 각 고유한 (a, b, c) 조합을 한 번만 계산
- 이후에는 저장된 값을 재사용
- 시간 복잡도: O(21 × 21 × 21) = O(9261) (a, b, c가 20 이하로 제한되므로)
해결 방법
메모이제이션을 사용하여 재귀 함수를 최적화합니다:
- 딕셔너리를 사용하여 계산 결과를 저장
- 함수 호출 시 이미 계산한 결과가 있으면 반환
- 없으면 계산하고 딕셔너리에 저장
코드 구현
# 메모이제이션을 위한 딕셔너리
wd = {}
def w(a: int, b: int, c: int):
global wd
# 기저 조건 1
if a <= 0 or b <= 0 or c <= 0:
return 1
# 기저 조건 2
if a > 20 or b > 20 or c > 20:
return w(20, 20, 20)
# 메모이제이션: 이미 계산한 결과가 있으면 반환
if (a, b, c) in wd:
return wd[(a, b, c)]
# 재귀 호출
if a < b and b < c:
result = w(a, b, c-1) + w(a, b-1, c-1) - w(a, b-1, c)
else:
result = w(a-1, b, c) + w(a-1, b-1, c) + w(a-1, b, c-1) - w(a-1, b-1, c-1)
# 결과를 메모에 저장
wd[(a, b, c)] = result
return result
# 입력 처리
while True:
a, b, c = map(int, input().split())
if a == -1 and b == -1 and c == -1:
break
print(f'w({a}, {b}, {c}) = {w(a, b, c)}')코드 설명
-
메모이제이션 딕셔너리:
wd는 (a, b, c) 튜플을 키로, 계산 결과를 값으로 저장합니다. -
기저 조건 처리:
a <= 0 or b <= 0 or c <= 0: 1 반환a > 20 or b > 20 or c > 20:w(20, 20, 20)호출
-
메모이제이션 체크:
(a, b, c) in wd로 이미 계산한 결과가 있는지 확인합니다. -
재귀 호출: 조건에 따라 적절한 재귀 호출을 수행합니다.
-
결과 저장: 계산한 결과를 딕셔너리에 저장하여 다음에 재사용합니다.
예제 동작 과정
w(1, 1, 1) 계산:
w(1, 1, 1)
→ else 조건 (a >= b or b >= c)
→ w(0, 1, 1) + w(0, 0, 1) + w(0, 1, 0) - w(0, 0, 0)
→ 모두 기저 조건 (a <= 0) → 1 반환
→ 1 + 1 + 1 - 1 = 2
→ wd[(1, 1, 1)] = 2 저장
w(2, 2, 2) 계산:
- w(1, 2, 2), w(1, 1, 2) 등을 계산
- w(1, 1, 1)은 이미 계산되어 있으므로 재사용
- 최종 결과: 4
성능 비교
| 방법 | w(15, 15, 15) 계산 시간 |
|---|---|
| 메모이제이션 없이 | 수 초 ~ 수 분 (시간 초과) |
| 메모이제이션 사용 | 수 밀리초 |
메모이제이션을 사용하면 수천 배 빠르게 계산할 수 있습니다!
주의사항
Note
원래 제공된 코드에서는
wd = {(1, 1, 1): 2}로 초기화되어 있었는데, 이는 불필요합니다. 메모이제이션은 함수 내에서 자동으로 처리되므로 초기값을 설정할 필요가 없습니다.