출처 : 백준, https://www.acmicpc.net/problem/1463
1463번: 1로 만들기
첫째 줄에 1보다 크거나 같고, 106보다 작거나 같은 정수 N이 주어진다.
www.acmicpc.net
첫 번째 풀이 : 실패
if __name__ == '__main__':
n = int(input())
count = 0
while n != 1:
if n % 2 == 1: #홀수
if n % 3 == 0:
n //= 3
else:
n -= 1
else:#짝수
if n % 3 == 0:
n //= 3
else:
if (n-1) % 3 == 0:
n -= 1
else:
n //= 2
count += 1
print(count)
125
10
#8
두 번째 풀이 : 실패
if __name__ == '__main__':
n = int(input())
count = 0
while n != 1:
if n % 2 == 1: #홀수
if n % 3 == 0:
n //= 3
else:
n -= 1
else:#짝수
if n % 3 == 0:
n //= 3
elif n % 3 == 1: #3n + 1인 짝수라면
if (n // 2) % 2 == 1 and (n // 2) % 3 != 0:
n -= 1
else:
n //= 2
else:
n //= 2
count += 1
print(count)
1000
10
#9
첫 번째 풀이와 두 번째 풀이는
n이 3의 배수이면 3으로, 짝수라면 2로, 둘 다 아니라면 -1을 1이 될 때까지 하는 방식의 변형들이다.
물론 둘 다 실패했다.
세 번째 풀이 : DP
if __name__ == '__main__':
n = int(input())
step = [0] * (n+1)
INF = 10e6 + 5
for i in range(2, n+1):
A = step[i // 3] if i % 3 == 0 else INF
B = step[i // 2] if i % 2 == 0 else INF
C = step[i - 1]
step[i] = min(A, B, C) + 1
print(step[n])
대체 뭐가 문제인가 싶어서 알고리즘 분류를 봤는데 다이나믹 프로그래밍인 것을 보고 머리가 띵했다.
그리고 DP라고 생각하니 정말 금방 문제가 풀리더라.
일단 DP이므로 점화식을 세워보자.
X가 1이 될 때 까지의 최소 횟수 step[X]라하면,
step[X] = min(step[X//3], step[X//2], step[X-1]) + 1(X≥2), step[1] = 0 (단, 나누어 떨어지지 않을 경우 무한)
라 세울 수 있다.
1에서부터 X까지 올라간다고 생각해보면 1에서 X까지 도달하기 위한 최소 횟수는
X//3까지의 최소 횟수와 X//2까지의 최소 횟수와 X-1까지의 최소 횟수 중 최소값에 +1을 한 것이기 때문이다.
시간 복잡도
O(n)
고찰
왜 DP인지 모르겠는데? 왜 DP인가?
항상 막히는 부분이 이런건데 어떤 알고리즘을 써야하는지 몰라서 잘못된 풀이를 작성해 문제를 풀지 못하는 경우가 많다.
이를 위해서 이 문제에서 DP를 써야하는 것을 어떻게 추론할 수 있는지 적어놓고자 한다.
먼저 이 문제는 1까지 도달하는데 걸리는 과정의 최소 횟수를 구하는 문제이다.
여기서 '최소 횟수'라는 말이 중요한데 즉, 이는 최솟값을 구하는 최적화 문제에 해당한다.
대학 알고리즘 과정 중 배우는 알고리즘은 다음과 같다.
1. Complexity
2. Recursion
3. Divide and Conquer + Sorting
4, 완전 탐색
5. Greedy
6. Backtracking
7. DP
8. Graph : BFS, DFS, Minimun Spanning Tree, Shortest Path
9. NP
이 중 최적화 문제에 쓰이는 대표적인 알고리즘은 바로
5. Greedy
7. DP
이다. 그렇다면 이 문제는 최적화 문제이므로 이 2가지 알고리즘으로 먼저 접근해보아야 하는데,
Greedy는 치명적인 단점을 가지고 있는 것이 바로 현재에 선택한 최선이 결과적으로 최선이어야 한다는 점이다.
이를 바탕으로 위 문제에서 현재에 최선인 값을 뽑아보면, X값을 가장 많이 줄일수록 최선이므로
X가 3의 배수일 때, 3으로 나누기
X가 2의 배수일 때, 2로 나누기
X가 둘 다 아닐 때, 1만큼 빼기
이다. 그리고 이는 예제 중 10이 10 -> 9 -> 3 -> 1로 진행되는 점만 보아도 아니라는 것을 알 수 있다.
10에서의 최선은 2의 배수이므로 2로 나누어 5가 되는 것인데, 실제 최소 횟수는 -1을 빼 9로 가는 방법이기 때문이다.
이를 통해 이 문제는 Greedy로 푸는 문제가 아니라고 결론 낼 수 있다.
(내가 실패한 첫 번째, 두 번째 풀이가 Greedy 방식으로 풀려 시도한 것이었다.)
그렇다면 DP인지 체크하기 위해 점화식을 세워봐야하는데, 점화식은 아래와 같이 바로 세워지는 것을 확인할 수 있다.
step[X] = min(step[X//3], step[X//2], step[X-1]) + 1(X≥2), step[1] = 0 (단, 나누어 떨어지지 않을 경우 무한)
여기서 이 문제는 DP로 풀어야한다는 것을 확정지을 수 있는 것이다.
즉, 오늘과 같은 실수를 하지 않기 위해서는
1. 무엇이 목표인지 먼저 파악하고 최적화 문제로 파악된다면, Greedy와 DP를 먼저 시도해본다.
2. Greedy가 쉬우므로 먼저 체크해보고, 아니거나 애매하다면 DP인지 확인하기 위해 점화식을 체크해본다.
'알고리즘 > Python' 카테고리의 다른 글
[python] 백준 6588 - 골드바흐의 추측 (0) | 2022.03.18 |
---|---|
[python] 백준 10610 - 30 (0) | 2022.03.17 |
[python] 백준 2581 - 소수 (0) | 2022.03.13 |
[python] 백준 1978 - 소수 찾기 (0) | 2022.03.13 |
[python] 백준 1292 - 쉽게 푸는 문제 (0) | 2022.03.12 |