https://www.acmicpc.net/problem/1463
풀이 1
정말 단순하게 생각하면,
"당연히 3으로 나누는게 가장 빨리 숫자를 줄일 수 있고, 그 다음이 2로 나누는 거고, 그 다음에 1을 빼는 것을 택하는 게 제일 유리한 거 아닌가?" 라고 생각할 수 있다. (약간의 Greedy 느낌!)
그러나, 10의 Input 예시만 생각해 봐도, 위와 같은 알고리즘으로는
- 10은 2로 나눠지므로, 10/2 = 5
- 5는 3, 2로 나눠지지 않으므로, 5-1=4
- 4는 2로 나눠지므로, 4/2 = 2
- 2는 2로 나눠지므로, 2/2 = 1
로 4번의 연산만에 1이 된다.
그러나, 첫 번째 단계에서 1을 빼는 것을 택한다면,
- 10 - 1 = 9
- 9 / 3 = 3
- 3 / 3 = 1
로 3번의 연산만에 1을 만들 수 있는 것을 볼 수 있다.
N = int(input()) cnt = 0 while N != 1: if N % 3 == 0: N /= 3 elif N % 2 == 0: N /= 2 else: N -= 1 cnt += 1 print(cnt)
풀이 2
따라서, 모든 연산 단계마다 3으로 나누는 경우, 2로 나누는 경우, 1을 빼는 경우를 모두 확인해 봐야 한다.
이를 효율적으로 계산하려면.. DP겠죠?
Top-Down 방식으로 풀었더니 재귀 횟수 초과(Recursion Error / 메모리 초과)가 뜸.
import sys sys.setrecursionlimit(10**6) N = int(input()) cnt = 0 '''dp[n] = n에서 1을 만들 수 있는 최소의 연산 횟수 (memoization)''' dp = dict([]) def dpdp(n): if n == 1: return 1 elif dp.get(n, -1) != -1: return dp[n] a = n b = n if n % 3 == 0: a = dpdp(int(n/3)) if n % 2 != 0: b = dpdp(int(n/2)) dp[n] = 1 + min(a, b, dpdp(n-1)) return dp[n] print(dpdp(N))
풀이 3
그럼 Bottom-Up으로 풀죠 뭐
N = int(input()) cnt = 0 '''dp[n] = n에서 1을 만들 수 있는 최소의 연산 횟수 (memoization)''' dp = dict([]) dp[1] = 0 n = 1 for i in range(1, N): dp[i+1] = min(dp.get(i+1, N), dp[i]+1) dp[i*2] = min(dp.get(i*2, N), dp[i]+1) dp[i*3] = min(dp.get(i*3, N), dp[i]+1) print(dp[N])