동적 계획법(Dynamic Programming)
- 최적화 이론의 한 기술
- 특정 범위까지의 값을 구하기 위해서 그것과 다른 범위까지의 값을 이용하여 효율적으로 값을 구하는 알고리즘
- 어떤 문제를 풀기 위해 그 문제를 더 작은 문제의 연장선으로 생각하고, 과거에 구한 해를 활용한다.
- ex. 피보나치 수열에서 중복된 연산을 미리 배열을 만들어두고 값을 저장해두면(메모이제이션) 시간 복잡도 O(N)으로 개선 가능
- 상향식 접근법 : for으로 구현, 작은 문제를 모아 큰 문제 해결, 메모제이션 사용X
- 하향식 접근법 : 재귀함수 구현, 큰 문제를 해결하기 위해 작은 재귀 함수 호출, 메모제이션 사용
- 보통 동적 계획법은 상향식 접근법을 사용
DP를 푸는 과정
- 테이블 정의하기
- 점화식 찾기
- 초기값 정하기
DP 문제 - 1463번 1로 만들기
- 정수 N이 주어질 때, 세 개의 연산을 적절히 사용해서 1을 만드려고 한다. 연산 사용 횟수의 최소값 구하기
1. 테이블 정의하기
- D[i] = i를 1로 만들기 위해 필요한 연산 사용 횟수의 최솟값
2. 점화식 찾기
- D[12] = ?
- 3으로 나누거나 D[12] = D[4] + 1
- 2으로 나누거나 D[12] = D[6] + 1
- 1을 빼거나 D[12] = D[11] + 1
- D[12] = min(D[4] + 1, D[6] + 1, D[11] + 1)
3. 초기값 정의하기
- D[1] = 0
n = int(input()) # 정수 N
dp = [0]*(n+1) # dp에 계산된 값을 저장해두고
dp[1] = 0
for i in range(2, n+1):
dp[i] = dp[i-1] + 1
if i % 3 == 0: dp[i] = min(dp[i], dp[i//3]+1)
if i % 2 == 0: dp[i] = min(dp[i], dp[i//2]+1)
print(dp[n])
DP 문제 - 9095번 1,2,3 더하기
- 정수 n이 주어질 때, n을 1,2,3의 합으로 나타내는 방법의 수 구하기
1. 테이블 정의하기
- D[i] = i를 1,2,3의 합으로 나타내는 방법의 수
2. 점화식 찾기
- D[4] = ?
- 1+1+1+1, 3+1, 2+1+1, 1+2+1 // (3을 1,2,3 합으로 나타내는 방법) + 1, D[3]
- 1+1+2, 2+2 // (2를 1,2,3 합으로 나타내는 방법) + 2, D[2]
- 1+3 // (1을 1,2,3 합으로 나타내는 방법) + 3, D[1]
- D[4] = D[1] + D[2] + D[3]
3. 초기값 정의하기
- D[1] = 1, D[2] = 2, D[3] = 4 방법의 개수
- D[i] = D[i-1] + D[i-2] + D[i-3]
tc = int(input())
for _ in range(tc):
n = int(input())
if n == 1:
print(1)
elif n == 2:
print(2)
elif n == 3:
print(4)
else:
dp = [0] * (n + 1)
dp[1], dp[2], dp[3] = 1, 2, 4
for i in range(4, n + 1):
dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3]
print(dp[n])
DP 문제 - 2579번 계단 오르기
- 각 계단에 쓰여 있는 점수가 주어질 때 게임에서 얻을 수 있는 총 점수의 최대값 구하기
1. 테이블 정의하기
- D[i][j] = 현재부터 j개의 계단을 연속해서 밟고 i번째 계단에서 얻을 수 있는 점수 합의 최대값 + i번째 계단은 반드시 밟아야 한다.
- (연속된 세 계단을 모두 밟아서는 안되는 제약조건)
2. 점화식 찾기
- D[k][1] = max(D[k-2][1], D[k-2][2]) + S[k] // 1개의 계단을 연속해서 밟고, k번째 계단까지 최대값
- 1개의 계단을 밟았다 = k-1번째는 밟지 않았다. = k-2번쨰는 무조건 밟았다.
- S[k] : k번째 계단 점수
- D[k][2] = D[k-1][1] + S[k] // 2개의 연속된 계단을 밟고, k번째 계단까지 최대값
- j는 3개 연속이 불가능하기 때문에 3 이상일 수 없다.
3. 초기값 정의하기
- D[1][1] = S[1], D[1][2] = 0
- D[2][1] = S[2], D[2][2] = S[1] + S[2]
n = int(input()) # 계단의 개수
S = [0]*(n+1)
for i in range(1, n+1):
S[i] = int(input())
if n == 1:
print(S[1])
elif n == 2:
print(S[1]+S[2])
else:
dp = [[0 for col in range(3)] for row in range(n+1)]
dp[1][1], dp[1][2] = S[1], 0
dp[2][1], dp[2][2] = S[2], S[1]+S[2]
# 계단 오르기 규칙
# 1. 한 계단 또는 두 계단 오르기 가능
# 2. 연속 세개 계단 불가능(시작점은 포함X)
# 3. 마지막 계단 무조건 밟기
for i in range(3,n+1):
dp[i][1] = max(dp[i-2][1], dp[i-2][2]) + S[i]
dp[i][2] = dp[i-1][1] + S[i]
print(max(dp[n][1], dp[n][2]))
DP 문제 - 1149번 RGB거리
- 집의 색상은 빨강, 초록, 파랑 중 하나의 색상
- 집을 빨강, 초록, 파랑으로 칠하는 비용이 주어질 때, 규칙을 만족하면서 모든 집을 칠하는 비용의 최솟값 구하기
1. 테이블 정의하기
- D[i][0] = i번째 집까지 칠할 때 비용의 최솟값, i번째 집은 빨강
- D[i][1] = i번째 집까지 칠할 때 비용의 최솟값, i번째 집은 초록
- D[i][2] = i번째 집까지 칠할 때 비용의 최솟값, i번째 집은 파랑
2. 점화식 찾기
- D[k][0] = min(D[k-1][1], D[k-1][2]) + R[k] // 빨강으로 칠할 때 k-1 집은 초록 or 파랑 + R[k] 빨강 비용
- D[k][1] = min(D[k-1][0], D[k-1][2]) + G[k] // 초록으로 칠하면 빨강 or 파랑
- D[k][2] = min(D[k-1][0], D[k-1][1]) + B[k] // 파랑으로 칠하면 빨강 or 초록
3. 초기값 정하기
- D[1][0] = R[1]
- D[1][1] = G[1]
- D[1][2] = B[1]
n = int(input()) # 집 개수
R = [0] * (n + 1)
G = [0] * (n + 1)
B = [0] * (n + 1)
dp = [[0 for col in range(3)] for row in range(n + 1)]
for i in range(1, n + 1):
color = list(input().split())
R[i] = int(color[0])
G[i] = int(color[1])
B[i] = int(color[2])
if n == 1:
print(min(R[1], G[1], B[1]))
else:
# 초기값 설정
dp[1][0] = R[1]
dp[1][1] = G[1]
dp[1][2] = B[1]
# 집 색칠 규칙
# 1. 1번의 집 색상은 2번의 집 색상과 같지 않아야 한다.
# 2. N번 집의 색상은 N-1번의 집 색상과 같지 않아야 한다.
# 3. 2(<= i <= N-1)번의 집 색상은 i-1번, i+1번 집의 색상과 같지 않아야 한다.
for k in range(2, n + 1):
dp[k][0] = min(dp[k - 1][1], dp[k - 1][2]) + R[k] # 이전색상 초록 or 파랑 + 현재 빨강
dp[k][1] = min(dp[k - 1][0], dp[k - 1][2]) + G[k] # 이전색상 파랑 or 빨강 + 현재 초록
dp[k][2] = min(dp[k - 1][0], dp[k - 1][1]) + B[k] # 이전색상 빨강 or 파랑 + 현재 파랑
print(min(dp[n][0], dp[n][1], dp[n][2]))
DP 문제 - 11726번 2xn 타일링
- 2xn 크기의 직사각형을 1x2, 2x1 타일로 채우는 방법의 수를 구하기
1. 테이블 정의하기
- D[i] = 2xi 크기의 직사각형을 채우는 방법의 수
2. 점화식 찾기
- D[i] = D[n-1] + D[n-2]
- 2 x 1 타일로 1개씩 채우면 남는 방법의 수 n-1
- 1 X 2 타일로 2개 채우면 남는 방법의 수 n-2
3. 초기값 정하기
- D[1] = 1
- D[2] = 2
n = int(input())
dp = [0]*1001
dp[1] = 1
dp[2] = 2
for i in range(3, 1001):
dp[i] = (dp[i-1] + dp[i-2]) % 10007
print(dp[n])
DP 문제 - 11659번 구간 합 구하기 4
- dp[i] = arr[1] + arr[2] + arr[3] + .. + arr[i]
- dp[i] = dp[i-1] + arr[i]
import sys
n, m = map(int, sys.stdin.readline().split()) # 수의 개수, 합을 구해야하는 횟수
arr = list(map(int, sys.stdin.readline().split()))
dp = [0]*(n+1)
# D[i] = A[1] + A[2] + .. + A[i]
# D[i] = D[i-1] + A[i]
for i in range(1, n+1):
dp[i] = dp[i-1] + arr[i-1]
for _ in range(m):
i, j = map(int, sys.stdin.readline().split())
print(dp[j]-dp[i-1])
'CS > 자료구조&알고리즘' 카테고리의 다른 글
[알고리즘] 깊이 우선 탐색 DFS(Depth First Search) (0) | 2022.09.18 |
---|---|
[알고리즘] 너비 우선 탐색 BFS(Breadth First Search) - 최단 경로 (0) | 2022.09.18 |
[알고리즘] 자료구조 - 스택, 큐, 덱, 힙, 우선순위 큐 (0) | 2022.09.16 |
[알고리즘] 해시(Hash), 파이썬 딕셔너리(Dictionary) (0) | 2022.09.15 |
[알고리즘] 투포인터, 슬라이딩 윈도우, 누적합, 구간합 (0) | 2022.09.14 |