1. 다이나믹 프로그래밍
다이나믹 프로그래밍(Dynamic Programming)이란 큰 문제를 작게 나누고, 같은 문제라면 한번씩만 풀어 문제를 효율적으로 해결하는 알고리즘 기법이다.
특정한 문제를 완전 탐색 알고리즘으로 접근했을 때, 시간이 매우 오래 걸리면 다이나믹 프로그래밍을 적용할 수 있는지 해결하고자 하는 부분 문제들의 중복 여부를 확인해보자.
ex) 피보나치 수열
피보나치 수열의 점화식은 다음과 같다. An = An-1 + An-2, A1 = 1, A2 = 1
(1) 피보나치 수열 재귀함수 코드 - 탑다운(Top-Down) 방식
d = [0] * 100 #한번 계산된 결과를 메모이제이션 하기 위한 리스트 초기화
def fibo(x):
if x == 1 or x == 2: #종료조건(1 또는 2일때 1 반환)
return 1
if d[x] != 0: #이미 계산한 적 있는 문제라면 그대로 반환
return d[x]
d[x] = fibo(x - 1) + fibo(x - 2) #아직 계산하지 않은 문제라면 점화식에 따라서 피보나치 결과 반환
return d[x]
print(fibo(99))
(2) 피보나치 수열 반복문 코드 - 보텀업(Bottom-Up) 방식
d = [0] * 100 #계산된 결과 저장하기 위한 DP 테이블 초기화, 크기는 주어지는 input이 아니라 최대크기로 잡기
d[1] = 1 #첫번째 피보나치 수는 1
d[2] = 1 #두번째 피보나치 수는 1
n = 99
for i in range(3, n + 1):
d[i] = d[i - 1]+ d[i - 2]
print(d[n])
2. 실전 문제
(1) 1로 만들기
Q
정수 X가 주어질 때, 정수 X에 사용할 수 있는 연산은 다음과 같이 4가지 이다.
1) X가 5로 나누어 떨어지면, 5로 나눈다.
2) X가 3으로 나누어 떨어지면, 3으로 나눈다.
3) X가 2로 나누어 떨어지면, 2로 나눈다.
4) X에서 1을 뺀다.
정수 X가 주어졌을 때, 연산 4개를 적절히 사용하여 1을 만들려고 한다. 연산을 사용하는 횟수의 최솟값을 출력하시오.
입력조건
첫째 줄에 정수 X가 주어진다(1<=X<=30000)
출력조건
첫째 줄에 연산을 하는 횟수의 최솟값을 출력한다.
A
다이나믹 프로그래밍은 점화식으로 표현할 수 있는지가 중요.
ai = min(ai-1, ai/2, ai/3, ai/5) + 1
1) 나의 코드
n = int(input())
count = 0
while n > 1:
if n % 5 == 0:
n /= 5
count += 1
elif (n - 1) % 5 == 0:
n -= 1
count += 1
elif n % 3 == 0:
n /= 3
count += 1
elif (n - 1) % 3 == 0:
n -= 1
count += 1
elif n % 2 == 0:
n /= 2
count +=1
else:
n -= 1
count += 1
print(count)
2) 다이나믹 프로그래밍 코드
x = int(input()) #정수 X 입력받기
d = [0] * 30001 #DP 테이블 초기화, 정수 X의 범위가 30001이므로
for i in range(2, x + 1): #다이나믹 프로그래밍 진행
d[i] = d[i - 1] + 1 #현재의 수에서 1을 빼는 경우
if i % 2 == 0: #현재의 수가 2로 나누어 떨어지는 경우
d[i] = min(d[i], d[i//2] + 1) #1을 빼는 경우와 2로 나누는 것 중에 최솟값으로 d에 저장
if i % 3 == 0: #현재의 수가 3으로 나누어 떨어지는 경우
d[i] = min(d[i], d[i//3] + 1) #1을 빼는 경우와 3으로 나누는 것 중에 최솟값으로 d에 저장
if i % 5 == 0: #현재의 수가 5로 나누어 떨어지는 경우
d[i] = min(d[i], d[i//5] + 1) #1을 빼는 경우와 5로 나누는 것 중에 최솟값으로 d에 저장
print(d[x])
(2) 개미 전사
Q
개미 전사는 부족한 식량을 충당하고자 메뚜기 마을의 식량창고를 몰래 공격하려고 한다. 메뚜기 마을에는 여러 개의 식량창고가 있는데 식량창고는 일직선으로 이어져 있다. 각 식량창고에는 정해진 수의 식량을 저장하고 있으며 개미 전사는 식량창고를 선택적으로 약탈하여 식량을 빼앗을 예정이다. 이때 메뚜기 정찰병들은 일직선상에 존재하는 식량창고 중에서 서로 인접한 식량창고가 공격받으면 바로 알아챌 수 있다. 따라서 개미 전사는 정찰병에서 들키지 않고 식량창고를 약탈하기 위해서는 최소한 한 칸 이상 떨어진 식량창고를 약탈해야 한다.
개미 전사를 위해 식량창고 N개에 대한 정보가 주어졌을 때, 얻을 수 있는 식량의 최댓값을 구하는 프로그램을 작성하시오.
입력조건
첫째 줄에 식량창고 개수 N이 주어진다(3<=N<=100).
둘째 줄에 공백으로 구분되어 각 식량창고에 저장된 식량의 개수 K가 주어진다(0<=K<=1000).
출력조건
첫째 줄에 개미 전사가 얻을 수 있는 식량의 최댓값을 출력하시오.
A
점화식 Ai = max(Ai-1, Ai-2 + ki)
코드
n = int(input())
array = list(map(int, input().split()))
d = [0] * 100 #DP 테이블 초기화(정수 N의 범위를 기준)
d[0] = array[0]
d[1] = max(array[0], array[1])
for i in range(2, n):
d[i] = max(d[i - 1], d[i - 2] + array[i]) #점화식 표현
print(d[n - 1]) #n - 1로 하는 이유는 d는 리스트로서 인덱스 0부터 시작하기 때문
(3) 바닥 공사
Q
가로의 길이가 N, 세로의 길이가 2인 직사각형 형태의 얇은 바닥이 있다. 이 2 X N의 얇은 바닥을 1X2의 덮개, 2X1의 덮개, 2X2의 덮개를 이용해 채우고자 한다. 이때 바닥을 채우는 모든 경우의 수를 구하는 프로그램을 작성하시오.
입력조건
첫째 줄에 N이 주어진다(1<=N<=1000).
출력조건
첫째 줄에 2 X N 크기의 바닥을 채우는 방법의 수를 796796으로 나눈 나머지를 출력한다.
A
796796으로 나누는 이유는 단지 결과값이 굉장히 커질 수 있기 때문이다. 그냥 무시하고 마지막에 나누기만 해주면 된다.
다이나믹 프로그래밍으로 푸는 힌트는 그림으로 생각하면 쉽다.
왼쪽부터 i-1까지의 길이에 덮개가 다 채워져 있을 경우에는 2X1 덮개를 채우는 하나의 방법밖에 없다.
왼쪽에서 i-2까지의 길이에 덮개 다 채워져 있는 경우에는 2X2의 덮개 또는 2X1 덮개 2개로 채우는 두가지 방법이 있다.
i-3부터는 생각할 필요가 없는 이유는 사용할 수 있는 덮개의 형태가 최대 2X2이기 때문이다.
따라서 점화식은 Ai = Ai-1 + Ai-2 * 2 가 된다.
코드
n = int(input()) #정수 입력
d = [0] * 1000 #DP 테이블 초기화
d[0] = 1 #d[1]로 해도 된다. 그럴 경우에는 range 범위랑 print해주는 값을 수정해주면 된다.
d[1] = 3
for i in range(2, n):
d[i] = (d[i-1] + d[i-2]*2) % 796796
print(d[n - 1])
(4) 효율적인 화폐 구성
Q