μ½”λ”©ν…ŒμŠ€νŠΈ/μ•Œκ³ λ¦¬μ¦˜

[C++ μ•Œκ³ λ¦¬μ¦˜] 동적 κ³„νšλ²•(Dynamic Programming)

kite707 2021. 7. 31.

κ°œλ…

λ™μ κ³„νšλ²•μ˜ μ •μ˜λŠ” μ•„λž˜μ™€ κ°™λ‹€.

처음 주어진 문제λ₯Ό 더 μž‘μ€ λ¬Έμ œλ“€λ‘œ λ‚˜λˆˆ λ’€ 각 쑰각의 닡을 κ³„μ‚°ν•˜κ³ , 이 λ‹΅λ“€λ‘œλΆ€ν„° μ›λž˜ λ¬Έμ œμ— λŒ€ν•œ 닡을 κ³„μ‚°ν•˜λŠ” 방식.

μ •μ˜λ§Œ κ°€μ§€κ³ λŠ” μ΄ν•΄ν•˜κΈ° 쉽지 μ•Šλ‹€. λ’€μ—μ„œ μ„€λͺ…ν•˜κ² μ§€λ§Œ, 결둠적으둜 λ§ν•˜μžλ©΄ 배열을 λ§Œλ“€μ–΄ 값듀을 μ €μž₯ν•˜κ³ , 후에 그것듀을 가지고 닡을 κ΅¬ν•˜λŠ” 방법이닀.

 

동적 κ³„νšλ²•μ„ μ΄μš©ν•΄ 문제λ₯Ό ν’€λ €κ³  ν•œλ‹€λ©΄ λ¨Όμ € 점화식을 μ„Έμ›Œμ•Ό ν•œλ‹€. μ ν™”μ‹μ΄λΌλŠ” 것은 μ–΄λ–€ μˆ˜μ—΄μ˜ μΌλ°˜ν•­μ„ μ΄μ „μ˜ 항듀을 μ΄μš©ν•΄ μ •μ˜ν•œ 식이닀. 고등학ꡐ λ•Œ λ°°μ› λ˜ μˆ˜μ—΄μ˜ μΌλ°˜ν•­μ„ κ΅¬ν•˜λ“―μ΄ n번째 항을 κ΅¬ν•˜λŠ” 방법을 μ‹μœΌλ‘œ λ‚˜νƒ€λ‚Έλ‹€κ³  μƒκ°ν•˜λ©΄ 쉽닀. 예λ₯Ό λ“€μ–΄ ν”νžˆλ“€ μ•„λŠ” ν”Όλ³΄λ‚˜μΉ˜ μˆ˜μ—΄μ˜ 점화식은 μ•„λž˜μ™€ κ°™λ‹€.

 

이제 이 식을 가지고 μ½”λ“œλ‘œ μž‘μ„±μ„ ν•΄ 보도둝 ν•˜μž. 일반적으둜 μž¬κ·€ν•¨μˆ˜λ₯Ό μ΄μš©ν•΄ μž‘μ„±μ„ ν•˜λŠ”λ° κ·Έλ ‡κ²Œ ν•˜λ©΄ μ•„λž˜μ™€ 같은 μ½”λ“œκ°€ λ‚˜μ˜¬ 것이닀.

#include <iostream>
using namespace std;

//ν”Όλ³΄λ‚˜μΉ˜ μˆ˜μ—΄μ„ κ³„μ‚°ν•˜λŠ” ν•¨μˆ˜ by μž¬κ·€
int fibo(int x) {
	if (x <= 1) return x;
	return fibo(x - 1) + fibo(x - 2);
}


int main() {
	int T;
	cin >> T;
	cout << fibo(T);
}

그런데 μ΄λ ‡κ²Œ 싀행을 ν•œλ‹€λ©΄ 컴퓨터가 같은 일을 ꡉμž₯히 많이 λ°˜λ³΅ν•˜κ²Œ λœλ‹€. 만일 ν”Όλ³΄λ‚˜μΉ˜ μˆ˜μ—΄μ˜ 6번째 항을 κ΅¬ν•œλ‹€κ³  κ°€μ •ν•˜μž. κ·Έλ ‡λ‹€λ©΄ μ»΄ν“¨ν„°λŠ” μ•„λž˜ μž‘μ—…λ“€μ„ μˆ˜ν–‰ν•˜κ²Œ λœλ‹€.

그림을 보면 fibo(2)λ₯Ό κ΅¬ν•˜λŠ” 과정이 3번 λ°˜λ³΅λœλ‹€. 이것 말고도 그림을 μžμ„Ένžˆ 보면 같은 μž‘μ—…μ„ 컴퓨터가 계속 λ°˜λ³΅ν•˜κ²Œ λœλ‹€λŠ” 것을 μ•Œ 수 μžˆλ‹€. fibo(3)을 κ΅¬ν•˜λŠ” 과정도 2λ²ˆμ΄λ‚˜ λ°˜λ³΅λœλ‹€. μˆ˜κ°€ 컀지면 컀질수둝 μ΄λŸ¬ν•œ μ€‘λ³΅λ˜λŠ” 과정은 κΈ°ν•˜κΈ‰μˆ˜μ μœΌλ‘œ μ¦κ°€ν•˜κ²Œ 될 것이닀. μ΄λ•Œ μ‚¬μš©ν•˜λŠ”κ²ƒμ΄ λ©”λͺ¨μ΄μ œμ΄μ…˜(memoization) 기법이닀. λ©”λͺ¨μ΄μ œμ΄μ…˜μ˜ μ •μ˜λŠ” μ•„λž˜μ™€ κ°™λ‹€.

컴퓨터 ν”„λ‘œκ·Έλž¨μ΄ λ™μΌν•œ 계산을 λ°˜λ³΅ν•΄μ•Ό ν•  λ•Œ, 이전에 κ³„μ‚°ν•œ 값을 λ©”λͺ¨λ¦¬μ— μ €μž₯ν•¨μœΌλ‘œμ¨ λ™μΌν•œ κ³„μ‚°μ˜ 반볡 μˆ˜ν–‰μ„ μ œκ±°ν•˜μ—¬ ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ 속도λ₯Ό λΉ λ₯΄κ²Œ ν•˜λŠ” κΈ°μˆ μ΄λ‹€. 

 

μ•žμ„œ "배열을 λ§Œλ“€μ–΄ 값듀을 μ €μž₯ν•œλ‹€." 라고 ν–ˆλŠ”λ° 이것이 λ©”λͺ¨μ΄μ œμ΄μ…˜ κΈ°λ²•μ˜ μ˜ˆμ‹œμ΄λ‹€. fibo(2) = fibo(1)+fibo(0)=2 λΌλŠ” 것을 ν•œλ²ˆ κ΅¬ν–ˆλ‹€λ©΄ 이것을 λ°°μ—΄μ˜ 2번째 μΈλ±μŠ€μ— μ €μž₯ν•˜κ³ (μ²«λ²ˆμ§Έμ— μ €μž₯해도 λ˜μ§€λ§Œ μ§κ΄€μ μœΌλ‘œ μ‚¬μš©ν•˜κΈ° μœ„ν•΄ 0번 μΈλ±μŠ€λŠ” 주둜 λΉ„μ›Œλ‘”λ‹€.) λ‚˜μ€‘μ— 또 fibo(2)의 값이 ν•„μš”ν•΄μ§€λ©΄ λ‘λ²ˆμ§Έ μΈλ±μŠ€μ— μžˆλŠ” 값을 κΊΌλ‚΄μ™€μ„œ μ‚¬μš©ν•˜λŠ” 것이닀. κ·ΈλŸ¬λ‹ˆ μ•žμ„œ μž‘μ„±ν•œ μž¬κ·€μš©λ²•μ— 방금 배운 동적 κ³„νšλ²•μ„ μ μš©ν•˜λ €λ©΄ 두가지λ₯Ό μΆ”κ°€ν•˜λ©΄ λœλ‹€.

  • λ§Œμ•½ λ°°μ—΄μ˜ ν•΄λ‹Ή μΈλ±μŠ€κ°€ 0이 μ•„λ‹ˆλΌλ©΄(λΉ„μ–΄μžˆμ§€ μ•Šλ‹€λ©΄) κ·Έ 값을 λ¦¬ν„΄ν•œλ‹€
  • 값을 배열에 μ €μž₯ν•œ λ’€ κ·Έ 값을 λ¦¬ν„΄ν•œλ‹€.

핡심은 μ—¬νƒœκΉŒμ§€λŠ” 값을 λ¦¬ν„΄ν•˜κΈ°λ§Œ ν–ˆμ§€λ§Œ μ΄μ œλŠ” 배열에 값을 μ €μž₯ν•œλ‹€λŠ” 것이닀. μ•„λž˜ μ½”λ“œλ₯Ό 보면 배열에 값을 μ €μž₯ν•˜λŠ” 뢀뢄이 μΆ”κ°€λ˜μ–΄ μžˆλ‹€.

#include <iostream>
using namespace std;

//ν”Όλ³΄λ‚˜μΉ˜ μˆ˜μ—΄μ˜ 값을 μ €μž₯ν•  λ°°μ—΄
int arr[10001];

//ν”Όλ³΄λ‚˜μΉ˜ μˆ˜μ—΄μ„ κ³„μ‚°ν•˜λŠ” ν•¨μˆ˜ by λ™μ κ³„νšλ²•
int fibo(int x) {
	//초기 쑰건, fibo(0)=0 fibo(1)=1
	if (x <= 1) {
		arr[x] = x;
		return arr[x];
	}
	//λ§Œμ•½ 이미 값을 κ΅¬ν•œ 경우라면 μ•žμ„œ κ΅¬ν•œ 값을 μ‚¬μš©ν•œλ‹€.
	if (arr[x] != 0) {
		return arr[x];
	}
	arr[x] = fibo(x - 1) + fibo(x - 2);
	return arr[x];
}


int main() {
	int T;
	cin >> T;
	cout << fibo(T);
}

μœ„ μ½”λ“œλ₯Ό 보면 μ•Œ 수 μžˆλ“―μ΄ 이미 값을 κ΅¬ν–ˆλ‹€λ©΄ μž¬κ·€ν•¨μˆ˜λ₯Ό λ‹€μ‹œ ν˜ΈμΆœν•˜λŠ”κ²ƒμ΄ μ•„λ‹ˆλΌ κ΅¬ν•œ 값을 μ‚¬μš©ν•œλ‹€λŠ” 것이닀. 즉 같은 값을 λ‹€μ‹œ 계산할 ν•„μš”κ°€ μ—†λ‹€.

 

μ‹œκ°„λ³΅μž‘λ„

μ‹œκ°„λ³΅μž‘λ„λŠ” 어렡지 μ•Šκ²Œ ꡬ할 수 μžˆλ‹€. 

λ°°μ—΄μ˜ 크기(ν˜Ήμ€ ν…Œμ΄λΈ”μ˜ 크기) X ν•œ 칸을 μ±„μšΈλ•Œ κ±Έλ¦¬λŠ” μ‹œκ°„

μ΅œμ•…μ˜ 경우 λͺ¨λ“  배열을 μ±„μ›Œμ•Ό ν•˜κΈ° λ•Œλ¬Έμ— 식이 μœ„μ™€κ°™μ΄ λ‚˜μ˜¨λ‹€. 만일 ν”Όλ³΄λ‚˜μΉ˜ μˆ˜μ—΄μ„ 동적 κ³„νšλ²•μœΌλ‘œ κ΅¬ν˜„ν–ˆμ„ κ²½μš°μ—, μ΅œμ•…μ˜ 경우 n번째 ν•­κΉŒμ§€ 배열을 μ±„μ›Œμ•Ό ν•˜κΈ° λ•Œλ¬Έμ— λ°°μ—΄μ˜ ν¬κΈ°λŠ” n, ν•œμΉΈμ„ μ±„μš°λŠ” 데에 κ±Έλ¦¬λŠ” μ‹œκ°„μ€ μΌμ •ν•œ μ‹œκ°„. 즉 1이기 λ•Œλ¬Έμ— μœ„ μ½”λ“œμ˜ μ‹œκ°„λ³΅μž‘λ„λŠ” O(n)이닀.

 

Top-down vs Bottom-up

동적 κ³„νšλ²•μ„ κ΅¬ν˜„ν•˜λŠ” λ°©λ²•μ—λŠ” 크게 두가지가 μžˆλŠ”λ° μœ„μ—μ„œ κ΅¬ν˜„ν•œ λŒ€λ‘œ μž¬κ·€λ₯Ό μ΄μš©ν•΄ κ΅¬ν•˜λŠ” 방식이 Top-down방식이닀. μœ„ λ°©λ²•μ—μ„œ int x, 즉 ν•¨μˆ˜ 인자 뢀뢄에 λ“€μ–΄μ˜€λŠ” μˆ˜κ°€ 점점 μž‘μ•„μ§„λ‹€(5->4->3 ...) 이제 Bottom-up방식에 λŒ€ν•΄ μ•Œμ•„λ³΄μž. λ‘κ°€μ§€μ˜ 차이점은 μ•„λž˜μ™€ κ°™λ‹€.

Top-down Bottom-up
큰 문제λ₯Ό μͺΌκ°œλ‚˜κ°„λ‹€. μž‘μ€ λ¬Έμ œλΆ€ν„° μ˜¬λΌκ°„λ‹€.
μž¬κ·€λ‘œ κ΅¬ν˜„ν•œλ‹€. 반볡문으둜 κ΅¬ν˜„ν•œλ‹€.

그럼 λ°˜λ³΅λ¬Έμ„ μ΄μš©ν•΄μ„œ μ•žμ„œ λ§Œλ“  ν”Όλ³΄λ‚˜μΉ˜ ν•¨μˆ˜λ₯Ό λ‹€μ‹œ λ§Œλ“€μ–΄ 보자.

#include <iostream>
using namespace std;

int arr[10001];

int main() {
	int T;
	cin >> T;

	//μ΄ˆκΈ°κ°’ μ„ΈνŒ…
	arr[0] = 0;
	arr[1] = 1;

	for (int i = 2; i <= T; i++) {
		arr[i] = arr[i - 1] + arr[i - 2];
	}
	cout << arr[T];

}

μœ„ μ½”λ“œλ₯Ό 보면 i의 값이 2λΆ€ν„° TκΉŒμ§€ μ¦κ°€ν•˜λŠ” 것을 λ³Ό 수 μžˆλ‹€. μœ„μ™€ 같이 μž‘μ€ λ¬Έμ œλΆ€ν„° κ΅¬ν•˜λŠ” 것을 Bottom-up방식이라고 ν•œλ‹€.

 

같이 ν’€λ©΄ 쒋은 문제

μ•„λž˜ λ¬Έμ œλ“€μ€ λ‚΄κ°€ dpλ₯Ό 배우고 ν’€μ—ˆλ˜ λ¬Έμ œλ“€μ΄λ‹€. 

 

 

μœ„μ—μ„œ λ‹€λ€˜λ˜ ν”Όλ³΄λ‚˜μΉ˜ μˆ˜μ—΄μ„ 쑰금 μ‘μš©ν•œ λ¬Έμ œμ΄λ‹€.

https://www.acmicpc.net/problem/1003

 

1003번: ν”Όλ³΄λ‚˜μΉ˜ ν•¨μˆ˜

각 ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ§ˆλ‹€ 0이 좜λ ₯λ˜λŠ” νšŸμˆ˜μ™€ 1이 좜λ ₯λ˜λŠ” 횟수λ₯Ό 곡백으둜 κ΅¬λΆ„ν•΄μ„œ 좜λ ₯ν•œλ‹€.

www.acmicpc.net

 


dpλ₯Ό μ΄μš©ν•΄μ„œ ν’€ 수 μžˆλŠ” λ¬Έμ œμ΄λ‹€. 점화식을 μ–΄λ–»κ²Œ μ„ΈμšΈμ§€ μƒκ°ν•˜λ©° ν’€μ–΄λ³΄μž.

https://www.acmicpc.net/problem/1463

 

1463번: 1둜 λ§Œλ“€κΈ°

첫째 쀄에 1보닀 ν¬κ±°λ‚˜ κ°™κ³ , 106보닀 μž‘κ±°λ‚˜ 같은 μ •μˆ˜ N이 주어진닀.

www.acmicpc.net

<풀이>

https://sectumsempra.tistory.com/84

 

[λ°±μ€€ 1463] -1λ‘œλ§Œλ“€κΈ°(C++)/λ‹€μ΄λ‚˜λ―Ή ν”„λ‘œκ·Έλž˜λ°

https://www.acmicpc.net/problem/1463 1463번: 1둜 λ§Œλ“€κΈ° 첫째 쀄에 1보닀 ν¬κ±°λ‚˜ κ°™κ³ , 106보닀 μž‘κ±°λ‚˜ 같은 μ •μˆ˜ N이 주어진닀. www.acmicpc.net 접근은 잘 ν–ˆλŠ”λ° 경우의 수λ₯Ό 각각 λΉ„κ΅ν•΄μ„œ μ΅œμ†Ÿκ°’μœΌλ‘œ λ‹΅..

sectumsempra.tistory.com

 


μ–΄λ–»κ²Œ 계단을 μ˜¬λΌκ°€μ•Ό 졜고점수λ₯Ό 얻을 수 μžˆλŠ”μ§€ μƒκ°ν•˜λ©° 문제λ₯Ό 풀도둝 ν•˜μž. μ—°μ†λœ 3개의 계단을 λ°Ÿμ§€ μ•ŠκΈ° μœ„ν•΄μ„œλŠ” μ–΄λ–»κ²Œ ν•΄μ•Ό 할지 μƒκ°ν•΄λ³΄μž. μ–΄λ ΅λ‹€λ©΄ μ•„λž˜ 힌트λ₯Ό 보고 λ‹€μ‹œ λ„μ „ν•΄λ³΄μž. (νžŒνŠΈλŠ” μ•„λž˜ 흐린 κΈ€μ”¨λ‘œ μ ν˜€μžˆλ‹€.)

https://www.acmicpc.net/problem/2579

 

2579번: 계단 였λ₯΄κΈ°

계단 였λ₯΄κΈ° κ²Œμž„μ€ 계단 μ•„λž˜ μ‹œμž‘μ λΆ€ν„° 계단 κΌ­λŒ€κΈ°μ— μœ„μΉ˜ν•œ λ„μ°©μ κΉŒμ§€ κ°€λŠ” κ²Œμž„μ΄λ‹€. <κ·Έλ¦Ό 1>κ³Ό 같이 각각의 κ³„λ‹¨μ—λŠ” μΌμ •ν•œ μ μˆ˜κ°€ μ“°μ—¬ μžˆλŠ”λ° 계단을 밟으면 κ·Έ 계단에 μ“°μ—¬ μžˆλŠ” 점

www.acmicpc.net

 

 

 

 

 

 

 

힌트 : n번째 κ³„λ‹¨κΉŒμ§€ 였λ₯΄λŠ” κ²½μš°λŠ” n-3κΉŒμ§€ 였λ₯΄κ³  2μΉΈ 1칸을 였λ₯΄κ±°λ‚˜, n-2κΉŒμ§€ 였λ₯΄κ³  2칸을 였λ₯΄λŠ” κ²½μš°κ°€ μžˆλ‹€. λ‘˜ 쀑 μ–΄λ–€ 값이 μ΅œλŒ“κ°’μΌκ°€? (만일 n-1κΉŒμ§€ 였λ₯΄κ³  ν•œμΉΈμ„ 더 μ˜¬λΌκ°„λ‹€κ³  ν•  경우, μ—°μ†ν•΄μ„œ μ„Έ 계단을 λ°ŸλŠ” κ²½μš°κ°€ λ°œμƒν•  수 μžˆλ‹€.)


2xn타일링은 1, 2κ°€ 각각 μžˆλŠ”λ° 만일 ν‘ΈλŠ” 방법을 λͺ°λžλ‹€λ©΄ 1을 ν‘ΈλŠ” 방법을 μ΄ν•΄ν•˜κ³ , 2λ₯Ό 혼자 풀어보도둝 ν•˜μž. μ•„μ΄λ””μ–΄λ§Œ λ– μ˜¬λ¦°λ‹€λ©΄ κ΅¬ν˜„ν•˜λŠ”κ²ƒμ€ 크게 어렡지 μ•Šλ‹€.

 

2xn타일링

https://www.acmicpc.net/problem/11726

 

11726번: 2×n 타일링

2×n 크기의 μ§μ‚¬κ°ν˜•μ„ 1×2, 2×1 νƒ€μΌλ‘œ μ±„μš°λŠ” λ°©λ²•μ˜ 수λ₯Ό κ΅¬ν•˜λŠ” ν”„λ‘œκ·Έλž¨μ„ μž‘μ„±ν•˜μ‹œμ˜€. μ•„λž˜ 그림은 2×5 크기의 μ§μ‚¬κ°ν˜•μ„ μ±„μš΄ ν•œ 가지 λ°©λ²•μ˜ μ˜ˆμ΄λ‹€.

www.acmicpc.net

2xn타일링 2

https://www.acmicpc.net/problem/11727

 

11727번: 2×n 타일링 2

2×n μ§μ‚¬κ°ν˜•μ„ 1×2, 2×1κ³Ό 2×2 νƒ€μΌλ‘œ μ±„μš°λŠ” λ°©λ²•μ˜ 수λ₯Ό κ΅¬ν•˜λŠ” ν”„λ‘œκ·Έλž¨μ„ μž‘μ„±ν•˜μ‹œμ˜€. μ•„λž˜ 그림은 2×17 μ§μ‚¬κ°ν˜•μ„ μ±„μš΄ ν•œκ°€μ§€ μ˜ˆμ΄λ‹€.

www.acmicpc.net


이 λ¬Έμ œλŠ” νŠΉμ΄ν•˜κ²Œ bottom-up을 μ μš©ν•œλ‹€.

https://www.acmicpc.net/problem/17626

 

17626번: Four Squares

λΌκ·Έλž‘μ£ΌλŠ” 1770년에 λͺ¨λ“  μžμ—°μˆ˜λŠ” λ„· ν˜Ήμ€ κ·Έ μ΄ν•˜μ˜ 제곱수의 ν•©μœΌλ‘œ ν‘œν˜„ν•  수 μžˆλ‹€κ³  증λͺ…ν•˜μ˜€λ‹€. μ–΄λ–€ μžμ—°μˆ˜λŠ” 볡수의 λ°©λ²•μœΌλ‘œ ν‘œν˜„λœλ‹€. 예λ₯Ό λ“€λ©΄, 26은 52κ³Ό 12의 합이닀; λ˜ν•œ 42 + 32 + 1

www.acmicpc.net


이 외에도 μ•„λž˜ 링크(λ°±μ€€-λ‹€μ΄λ‚˜λ―Ή ν”„λ‘œκ·Έλž˜λ°)에 λ“€μ–΄κ°€λ©΄ dpλ₯Ό μ΄μš©ν•΄ ν‘ΈλŠ” λ§Žμ€ λ¬Έμ œλ“€μ„ λ³Ό 수 μžˆλ‹€.

https://www.acmicpc.net/problemset?sort=ac_desc&algo=25 

 

문제 - 1 νŽ˜μ΄μ§€

 

www.acmicpc.net

 

λŒ“κΈ€