68
-1- Chapter 1 문제 및 풀이 채점가능사이트 정올 : (www.jungol.co.kr ) 안의 번호는 정올사이트의 문제번호입니다 () .

Chapter 문제및풀이 - JUNGOL · 동적계획법(DynamicProgramming) - 3 - 문제풀이차례

Embed Size (px)

Citation preview

- 1 -

Chapter 1문제 및 풀이

채점가능사이트 정올: (www.jungol.co.kr)

안의 번호는 정올사이트의 문제번호입니다() .※

동적계획법 (Dynamic Programming)

- 3 -

문제풀이 차례

정렬(SORT) (1972) 4힙정렬 (Hesp_Sort) (2082) 6못생긴 수 (1318) 9미로탈출 게임 (3010) 11세 줄로 타일 깔기(2112) 14여러 줄로 타일 깔기 (1442) 27두부 모판 자르기 (1993) 29건물 세우기 (1249) 32해밀턴 순환회로2 (1545) 39교통수단 선택하기 (3008) 41

- 4 -

문제 정렬(SORT) (1972)

입력으로 주어진 자연수들을 오름차순 또는 내림차순으로 정렬하여 출력하여보자.

[ ]첫 줄에 이 주어진다 은 정렬 할 자연수의 개수이다N . N . (1 N 100,000)정렬방법 가 주어진다 값이 이면 오름차순 이면 내림차순으로 출력해야한다C . C 0 , 1 .개의 자연수가 주어진다 각 자연수는 억 이하의 수이다N . 10 .

[ ]정렬한 수들을 출력한다.

509251100

1259100

동적계획법 (Dynamic Programming)

- 5 -

풀이

퀵정렬 합병정렬 힙정렬 등의 알고리즘을 이용해(Quick Sort), (Merge Sort), (Heap Sort)서 정렬하면 된다.

여기에서는 합병정렬을 이용해서 정렬하는 방법을 소개하도록 한다.합병정렬은 주어진 배열을 둘로 나누어서 각각 정렬한 후 합병하는 방법으로 정렬하는것이다 나누어진 배열 역시 같은 방법으로 다시 둘로 나누어 정렬해서 합병하게 된다. .

정렬하는 과정을 살펴보면 다음과 같다.

정렬할 배열3 10 5 9 4 8 1 6

배열을 둘로 나눈다.3 10 5 9 4 8 1 6

왼쪽과 오른쪽을 각각 정렬한다.3 5 9 10 1 4 6 8

합병한다.1 3 4 5 6 8 9 10

합병정렬의 기본적인 함수 구조는 다음과 같다.m_sort(A[], s, e){if (s >= e) return; 정렬할 원소가 개 이하이면// 1int m = (s + e) / 2; 배열을 둘로 나눈다// . (s ~ m, m + 1 ~ e)m_sort(A, s, m); 왼쪽 정렬//m_sort(A, m + 1, e); 오른쪽 정렬//merge(A, s, m, e); 합병//

}

- 6 -

문제 힙정렬 (Hesp_Sort) (2082)

정올이는 정수 배열을 입력받아 빠르게 정렬하는 임무를 부여받았다.

어떤 방법으로 정렬할까 망설이던 정올이는 배열을 구조로 만들어서 정렬하max_heap기로 했다 그 과정은 다음과 같다. .우선 함수를 만들어 놓고 한 개씩 입력을 받을 때마다 함수를 실행해서 계push_heap속 의 구조가 유지되도록 하면서 입력을 모두 끝낸다max_heap .그리고 입력이 제대로 진행되었는지 확인하기 위해 모든 자료를 출력한다.

예를 들어 입력 자료가 이렇게 개라고 하면 입력받아서 처리하는 과정3 6 4 8 9 7 6은 아래와 같고 출력을 하면 가 된다9 8 7 3 6 4 .

입력이 끝나면 함수를 만들어서 첫 번째와 마지막 자료를 바꾼후pop_heap pop_heap을 호출하여 마지막 자료를 제외한 나머지가 의 구조가 유지되도록 하는 작max_heap업을 자료의 개수만큼 반복하여 정렬을 끝낸다 그리고 최종적으로 오름차순으로 정렬.된 자료를 모두 출력한다.

정올이를 도와 위와같이 출력하는 프로그램을 작성해 주자.

동적계획법 (Dynamic Programming)

- 7 -

[ ]첫 번째 줄에는 데이터의 개수 이 입력된다N . (1 N 500,000)두 번째 줄에 개의 숫자가 공백으로 구분되어 입력된다 개의 입력데이터N . (1 N억21 )

[ ]첫 번째 줄에는 입력을 받으면서 의 구조가 된 개의 숫자를 차례로 출력한max_heap N다 두 번째 줄에는 힙정렬을 끝내고 오름차순으로 정렬된 자료를 차례로 출력한다. .

63 6 4 8 9 7

9 8 7 3 6 43 4 6 7 8 9

- 8 -

풀이

힙정렬 은 힙이라는 자료구조를 이용하여 정렬하는 알고리즘이다(Heap Sort) .

여기서 힙이란 자료들을 완전 이진 트리 로 채우면서 부모노드가(complete binary tree)항상 크거나 항상 작은 구조를 유지하는 자료구조이다(max heap), (min heap) .따라서 최대값 또는 최소값이 항상 루트에 자리하게 된다.

힙에 자료를 삽입하는 과정은 다음과 같다 일 경우. (max heap )힙의 마지막에 자료를 추가한다- .부모노드와 비교하여 크면 교환하고 부모노드로 이동하여 같은 작업을 반복한다- .부모노드는 현재노드 에 위치한다- / 2 .

힙에서 자료를 제거하는 과정은 다음과 같다.힙의 루트와 마지막 자료를 바꾼다 현재 힙의 자료중 가장 큰 값이 마지막으로 이- . (동)마지막 자료를 제거한다 힙의 자료 개수를 한 개 줄인다- . ( .)루트의 값과 왼쪽 자식과 오른쪽 자식중 더 큰값과 비교하여 자식이 더 크면 교환한-다.교환한 자식의 위치로 가서 같은 작업을 반복한다- .왼쪽자식은 현재노드 오른쪽 자식은 현재노드 의 위치이다- * 2, * 2 + 1 .

위와 같은 작업을 번 반복하면 가장 큰 값부터 마지막으로 이동하여 정렬이 완료N - 1된다.

자세한 사항은 코드를 참조하기 바란다.

동적계획법 (Dynamic Programming)

- 9 -

문제 못생긴 수 (1318)

못생긴 수란 소인수분해 했을 경우 나오는 소인수가 그리고 뿐인 수를 이야기, 2, 3 5하며 이를 수열로 늘어놓으면 다음과 같다, .

1, 2, 3, 4, 5, 6, 8, 9, 10, 12...

이는 처음나오는 개의 못생긴 수이며 편의상 을 포함하도록 하자 정수 이 주어10 , 1 . n졌을 때 번째 못생긴 수를 출력하는 프로그램을 작성하라, n .

[ ]한 줄에 양의 정수 이 주어진다n(n 1,500) .입력에 이 주어질 때까지 계속 한다0 .

[ ]출력에는 번째 못생긴 수를 출력한다n .

1290

1210

- 10 -

풀이

최소힙 자료구조를 이용하면 쉽게 해결이 가능하다(Heap) .

기본적으로 첫 번째 수는 이다1 .그 수로 새로 만들 수 있는 수는 각각 이다2, 3, 5 .

그 중 최소값인 가 두 번째 수이다2 .그 수로 새로 만들 수 있는 수는 각각 이다 에 를 곱한 수4, 6, 10 . (2 2, 3, 5 )

남은 수들중 최소값인 이 세 번째 수이다3 .그 수로 새로 만들 수 있는 수는 각각 이다6, 9, 15 .

이렇게 현재 생성된 수들중 최소값을 찾아 나가면 된다.

처음에 을 힙에 넣고1힙에서 최소값을 꺼내서 하고 그 수로 만들 수 있는 수 개를 힙에 넣는다count 3 .

가 입력된 값이 될 때까지 위와 같은 작업을 반복하고 마지막으로 꺼낸 값을 출력count하면 된다.

같은 수가 중복될 수도 있으므로 최소값을 꺼낼 때 직전에 꺼낸 값과 같다면 를count하지 않으면 된다.

동적계획법 (Dynamic Programming)

- 11 -

문제 미로탈출 게임 (3010)

고양이 톰과 생쥐 제리는 미로탈출 놀이를 하고 있다.미로의 입구로 제리가 들어가는 것을 본 톰은 재빨리 제리를 추격하기 시작했다.

미로는 크기의 직사각형 모양으로 되어 있으며 개의 정사각형 격자 모N * M N * M양으로 구성되어 있다.각 격자에는 톰이나 제리가 통과할 수 있는 둥근 파이프가 놓여져 있는데 파이프가입구와 출구가 서로 연결이 되어야 진입이 가능하다.파이프의 모양은 일자 모양과 기역자 모양 두 가지가 있는데 방향에 따라 다음과 같이 번호를 부여하도록 하자.

1 2 3 4 5 6

미로의 입구는 왼쪽의 맨 위이며 출구는 오른쪽의 맨 아래쪽이다.입구와 출구에는 언제든 들어가고 나올 수 있도록 파이프가 고정되어 있으나 나머지위치에는 파이프가 무질서하게 놓여져 있다.

제리가 최대한 빠르게 출구로 빠져나갈 수 있도록 파이프를 정리해 주었을 때 제리가통과해야 하는 격자의 수를 출력하는 프로그램을 작성하라 각 파이프는 각각 도씩. 90횟수 제한없이 회전이 가능하지만 한번 진입했던 격자에는 다시 들어갈 수 없다.

- 12 -

[ ]입력의 첫 줄에는 행과 열의 크기 과 이 입력된다N M . (1 <= N, M <= 100)둘 째 줄부터 줄에 걸쳐 각 줄마다 개의 정수가 입력되는데 각 위치에 놓인 파이N M프의 모양을나타낸다.

[ ]각 위치의 파이프를 회전하여 입구부터 출구까지 이동이 가능하도록 할 때의 최소 이동거리 통과해야 하는 격자의 개수 를 출력한다( ) .통과할 수 있는 경로가 없다면 을 출력한다0 .

3 42 1 4 53 2 6 13 1 1 2

10

동적계획법 (Dynamic Programming)

- 13 -

풀이

가장 쉽게 생각할 수 있는 방법은 알고리즘이다BFS .의 요소는 좌표 와 방향 진입했을 때 나갈 수 있는 출구 그리고 진입레벨로queue (x, y) ( ),

넣고 이미 체크된 곳은 다시 방문하지 않도록 하면 문제를 해결할 수 있다.그런데 각 좌표마다 출구의 방향에 따라 번을 방문할 수 있으므로 같은 위치를 중복해4서 지나갈 위험이 있다 일반적인 자료의 경우에는 같은 위치를 중복해서 지나는 경우가.최단거리가 될 가능성이 거의 없기는 하지만 다음과 같이 다른 경로가 존재하지 않는데이터를 임의로 생성한다면 잘못된 출력이 나올 수도 있다.

위 경우에 실제로는 목적지에 도달할 수 있는 경로가 없지만 번째에 로 가로3 (2, 2, 2)로 통과한 후에 번째에 다시 로 지나갈 수 있게 된다7 (2, 2, 1) .

따라서 이러한 문제를 방지하기 위해서는 다소 시간이 소요되더라도 백트래킹 알(DFS)고리즘으로 해결이 가능할 수 있다.논리적으로는 시간이 초과될 수도 있을 것 같지만 오른쪽과 아래쪽을 우선순위로 호출하면서 같은 위치의 각 방향에 대한 최단거리 기록과 목적지에 도착한 최단거리 기록을바탕으로 가지치기를 잘 한다면 시간내에 무난히 문제를 해결할 수 있다.

호출하는 내용은 와 마찬가지로 좌표 출구방향 레벨을 매개변수로 넘기면서BFS (x, y), ,각 해당 위치에 현재까지의 최소도착 레벨을 기록해 두고 다음에 이 레벨이상으로 도착하는 경우 더 이상 진행할 필요가 없으므로 리턴한다.또한 현재의 레벨에서 도착지까지의 최단거리 맨하탄 거리 목적지의 좌표 현재 위( , x -치 좌표 목적지 좌표 현재 위치 좌표 를 더한 결과값이 현재까지 목적지에 도x + y - y )달한 최단거리 이상일 경우 더 좋은 결과를 기대할 수 없으므로 리턴하도록 구성하면불필요한 호출이 획기적으로 줄어들기 때문에 충분히 시간내에 결과를 얻을 수 있다.

물론 방향과 관계없이 현재까지 지나온 각 위치를 체크해 두고 체크된 위치는 지나갈수 없도록 해야 에서의 반례를 해결할 수 있다BFS .

- 14 -

문제 세 줄로 타일 깔기(2112)

바닥이 가로 칸 세로 칸으로 이루어졌을 때 가로 칸 세로 칸 회전 가n(n 30) , 3 , 1 , 2 (능하다 의 타일을 이용해 타일을 까는 경우의 수를 계산하자) .

[ ]첫 줄에 바닥의 가로 길이 이 입력된다n .

[ ]첫 줄에 가능한 경우의 수를 출력한다.

1 13 0

2 24 11

동적계획법 (Dynamic Programming)

- 15 -

풀이각 위치별 가능한 상태에 대한 경우의 수를 구하는 방법[ ]

가로의 각 위치에서 이전까지는 모두 채워져있다고 하면 현재의 위치에 놓여있는 형태는 아래와 같은 가지 경우가 가능하다8 .

1 2 3 4 5 6 7 8한 개도 채워지지 않은경우

한 개만 채워진 경우

두 개가 채워진 경우

세 개 모두채워진 경우

번의 경우는 이전 단계의 번과 같으므로 생략하기로 하고 번과 번의 경우는 실제로1 8 3 6는 가능하지 않은 경우에 해당하므로 번과 번을 묶어서 개가 채워진 경우 번과2 4 1 , 5 7번을 묶어서 개가 채워진 경우 번과 같이 모두 채워진 경우로 나누어서2 , 8

가로 번째에 개가 채워진 경우의 수로 정의하고D[i][j] = i j각각의 경우의 수를 구해나가면 다음과 같이 채워나갈 수 있다.

가로채워진수 0 1 2 3 4 5 6

1 2 8 302 ② 2 6 + 2 22 + 83 1① 1 + 2 3 + 8 11 + 30

가로의 첫 번째 이전까지는 모두 채워진 경우가 가지이다 즉 초기값은1 . , D[0][3] =이다1 .첫 번째 칸에 놓을 수 있는 방법은 위쪽 또는 아래쪽에 놓을 수 있는 두가지이다.즉 다음 단계의 칸짜리를 가지 만들 수 있다, 2 2 .가로로 개를 놓게 되면 가로 두칸이 모두 채워진다 즉 다음 다음 단계의 개 모두3 . 3채워진 형태가 된다.

- 16 -

두 개짜리에서 놓을 수 있는 방법은 빈곳에 가로로 놓는 방법 뿐이며 그렇게 되면다음단계의 개짜리가 된다1 .칸이 채워진 상태에서 세로로 빈곳을 채우면 개짜리가 되고1 3빈곳을 가로로 채우게 되면 다음 단계의 개짜리가 된다2 .

위와 같은 상태가 반복되므로 다음과 같은 점화식을 얻을 수 있다.D[i][1] = D[i-1][2];D[i][2] = D[i-1][3] * 2 + D[i-1][1];D[i][3] = D[i-2][3] + D[i][1];

동적계획법 (Dynamic Programming)

- 17 -

각 위치별 완성된 개수로 처리하는 방법[ ]

가로가 홀수일 경우에는 불가능하다.가로 칸을 채우는 방법은 다음과 같은 가지가 있다2 3 .

가로 칸을 채우는 방법은 위 칸을 채우는 방법 가지에 새로 칸을 채우는 방법 가4 2 3 2 3지를 조합하면 가지가 가능하고9

추가로 다음과 같이 칸을 연결하여 채울 수 있는 방법이 가지가 있다4 2 .

따라서 가 된다D[4] = D[2] * 3 + D[0] * 2 .

위와 같은 방법으로 칸을 연결하는 방법도 가지가 있으며6 2

따라서 칸을 채우는 방법은 칸을 채운 상태에서 칸짜리 가지를 조합하고6 4 2 3칸을 채운 상태에서 칸짜리 가지를 조합하고2 4 2칸에서 칸짜리 가지를 조합하면0 6 2

- 18 -

의 식이 성립된다D[6] = D[4] * 3 + D[2] * 2 + D[0] * 2 .

위의 내용을 토대로 의 점화식을 얻을 수D[i] = sum(D[0], D[i-2]) * 2 + D[i-2]있다.

은 부터 까지의 누적합을 의미하며 는 을sum(D[0], D[i-2]) D[0] D[i-2] D[i-2] 3곱해야 하므로 한번 더 더해 준 것이다.

동적계획법 (Dynamic Programming)

- 19 -

비트단위로 채워나가는 방법[ ]

가로 칸을 채워야 한다면 아래와 같은 배열을 모두 채우는 것과 같은 것이다6 .따라서 의 위치부터 순서대로 빈곳이 없도록 채워나가(0,0) (0,1), (0,2), (1,0), (1,1)....면서 경우의 수를 누적하여 저장해 나가는 방법으로 문제를 해결해 나간다.현재의 위치를 채울 경우 현재위치를 채우면서 다른곳이 함께 채워지므로 그 상태를 비트형태로 나타내서 경우의 수를 저장하는 것이다.

채워야 하는 모든 위치를 순서대로 번호로 나타내면 아래와 같이 표시할 수 있다.가로세로 0 1 2 3 4 50 0 3 6 9 12 151 1 4 7 10 13 162 2 5 8 11 14 17

각 번호 를 기준으로 번 이전은 모두 채워져 있고 이후는 모두 비어 있다고 가정i i i+3하고 번 곳 위치의 상태를 비트로 나타내면 다음과 같이 표시할 수 있다i+2. i+1. i 3 .은 비어있는 상태 은 이미 채워진 상태를 의미함(0 , 1 )i+2 i+1 i 이진수 십진수0 0 0 000 00 0 1 001 10 1 0 010 20 1 1 011 31 0 0 100 41 0 1 101 51 1 0 110 61 1 1 111 7

초기 상태는 모두 비어 있는 상태가 가지 이므로 이 된다1 D[0][0] = 1 .

만약 번이 채워져 있다면 번 위치에 놓을 수 없으므로 다음 단계인 의 위치에 이진i i i+1수를 오른쪽으로 쉬프트하여 로 나누어 경우의 수를 그대로 전달하면 된다(2 ) .

- 20 -

번이 비어있을 경우에는 번을 기준으로 가로와 세로를 모두 채워봐야 한다i i .세로로 채우기 위해서는 현재의 위치가 맨 아래가 아니고 번이 비어 있을 경우에i + 1한해서 채울수 있다.가로로는 번이 항상 비어 있으므로 항상 채울 수 있다i + 3 .

이 때 가로나 세로로 채우는 것을 현재 위치의 테이블에 기록하는 방법과 번 테이i + 1블에 바로 기록하는 방법이 있다.

먼저 현재 위치의 테이블에 기록하는 방법으로 생각해 보자.현재 테이블에 기록하기 위해서는 부터 번까지 비트를 관리해야 하므로 일단 테i i + 3 4이블의 크기를 24으로 만들어야 한다.

초기값은 이므로 번을 타일로 채우는 방법을 생각해보자D[0][0] = 1 0 .우선 세로로 타일을 채우게 되면 번과 번을 함께 채워야 되므로 이 경우 번과 번은0 1 3 2번과 번은 이 되어 이 되므로 진수로는 번을 만들 수 있게 된다0, 1 0 1 0011 10 3 .

가로로 타일을 채우게 되면 번과 번을 함께 채워야 하므로 이 되므로 번이 된0 3 1001 9다 즉 의 값으로 과 를 갱신하게 된다. (0, 0) (0, 3) (0, 9) .

ji 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 150 1 1 11

이 내용을 그림으로 살펴보면 다음과 같다 상태는 순으로 놓여있는지의 여부. ( 3 2 1 0를 이진수 형태로 나타낸 것이다.)0 3 6 9 12 151 4 7 10 13 162 5 8 11 14 17

0 3 6 9 12 151 4 7 10 13 162 5 8 11 14 17

0 3 6 9 12 151 4 7 10 13 162 5 8 11 14 17

초기상태 0000(0) 세로로 놓을 경우 0011(3) 가로로 놓을 경우 1001(9)

그리고 의 입장에서 번은 의 입장에서는 이 되므로 은 을 갱신해준다0 3 1 1 (0, 3) (1, 1) .같은 방법으로 는 를 갱신하게 된다(0, 9) (1, 4) .

동적계획법 (Dynamic Programming)

- 21 -

ji 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 150 1 1 11 1 1

이 내용을 그림으로 살펴보면 다음과 같다 을 기준으로 하면 번의 상태를 순. (1 4 3 2 1서대로 이진수 형태로 나타낸 것이다.

0 3 6 9 12 151 4 7 10 13 162 5 8 11 14 17

0 3 6 9 12 151 4 7 10 13 162 5 8 11 14 17

을 중심으로0 0011(3) 을 중심으로1 0001(1)0 3 6 9 12 151 4 7 10 13 162 5 8 11 14 17

0 3 6 9 12 151 4 7 10 13 162 5 8 11 14 17

을 중심으로0 1001(9) 을 중심으로1 0100(4)

이제 가 일 경우 다음 단계가 어떻게 갱신되는지 살펴보자i 1 .먼저 가 인 경우 이므로 이미 번의 위치가 채워져 있으므로 다음단계를 갱신한j 1 0001 1다 즉 은 을 갱신하게 된다. (1, 1) (2, 0) .0 3 6 9 12 151 4 7 10 13 162 5 8 11 14 17

0 3 6 9 12 151 4 7 10 13 162 5 8 11 14 17

을 중심으로1 0001(1) 를 중심으로2 0000(0)

가 인 경우는 이므로 세로로 놓을 경우 이 되고 가로로 놓을 경우는j 4 0100 0111(7)이 되므로 는 과 을 갱신하게 된다1101(13) (1, 4) (1, 7) (1, 13) .

그리고 은 을 은 을 갱신하게 된다(1, 7) (2, 3) (1, 13) (2, 6) .0 3 6 9 12 151 4 7 10 13 162 5 8 11 14 17

0 3 6 9 12 151 4 7 10 13 162 5 8 11 14 17

0 3 6 9 12 151 4 7 10 13 162 5 8 11 14 17

현재상태 0100(4) 세로로 놓을 경우 0111(7) 가로로 놓을 경우 1101(13)

- 22 -

0 3 6 9 12 151 4 7 10 13 162 5 8 11 14 17

0 3 6 9 12 151 4 7 10 13 162 5 8 11 14 17

을 중심으로1 0111(7) 를 중심으로2 0011(3)0 3 6 9 12 151 4 7 10 13 162 5 8 11 14 17

0 3 6 9 12 151 4 7 10 13 162 5 8 11 14 17

을 중심으로1 1101(13) 를 중심으로2 0110(6)ji 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

0 1 1 11 1 1 1 12 1 1 1

같은 방법으로 가 인 경우를 갱신해보자i 2 .의 위치는 맨 아래쪽이므로 세로로는 타일을 놓을 수 없다2 .

에서는 가로로 놓을 경우 번과 번이 채워지므로 를 갱신하게 된다(2, 0) 2 5 (2, 9) .은 번 위치가 채워져 있으므로 을 갱신하게 된다(2, 3) 2 (3, 1) .은 가로로 채우게 되면 가 갱신된다(2, 6) (2, 15) .ji 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

0 1 1 11 1 1 1 12 1 1 1 1 13 1 1 1

갱신하는 과정을 살펴보면 현재의 상태 에서 위치에 이미 놓여진 경우 즉 가 홀수(i, j) i j인 경우에는 다음 단계에서 를 오른쪽으로 쉬프트 연산 로 나눈 몫 한j (2 ) (i + 1, j / 2)를 갱신하게 되고위치가 비어 있는 경우에는 일단 가로로 놓는 가 갱신되고i (i, j + 9)세로로 놓을 수 있는 경우에는 이 갱신되는 것을 알 수 있다(i, j + 3) .

이러한 규칙을 바탕으로 다음과 같은 점화식을 얻을 수 있다.

동적계획법 (Dynamic Programming)

- 23 -

가 홀수인 경우j D[i + 1][j / 2] += D[i][j]가 짝수인 경우j D[i][j + 9] += D[i][j]맨 아래칸이 아니고 와 이 비어 있다면(i % 3 < 2) j j + 1 (j & 3 == 0)

D[i][j + 3] += D[i][j]

이러한 식으로 까지 테이블을 채워보면 아래은 결과를 얻을 수 있다(12, 0) .ji 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

0 1 1 11 1 1 1 12 1 1 1 1 13 1 1 2 14 1 3 1 1 15 3 1 1 16 3 4 1 3 17 4 3 4 38 4 4 3 4 39 4 4 7 410 4 11 4 4 411 11 4 4 412 11 4 4

만약 이 라면 테이블의 마지막인 에서 인 경우의 수 즉 또는 다음 단계에n 4 11 1 D[11][1]서 한 개도 채워지지 않은 이 답이 된다D[12][0] .

- 24 -

이번에는 번 위치가 비어 있을 경우 현재의 위치에서 갱신하지 않고 바로 다음의 위치i를 갱신하는 방법을 생각해 보자.

초기 상태는 모두 비어 있는 상태가 가지 이므로 이 된다1 D[0][0] = 1 .번을 채우기 위해서는 세로로 놓을 경우 과 함께 번을 채우게 되고 가로로 놓을 경0 0 1 ,우는 번을 함께 채우게 되어 과 를 갱신하는 방법은 이미 익힌바 있다3 (0, 3) (0, 9) .그리고 은 다시 을 는 를 갱신하게 된다(0, 3) (1, 1) (0, 9) (1, 4) .

ji 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 150 1 1 11 1 1

위와 같은 절차에서 세로로 놓을 경우 을 가로로 놓을 경우 를 갱신하는(0, 3) , (0, 9)절차를 생략하고 바로 과 를 바로 갱신하도록 하자는 것이다(1, 1) (1, 4) .

ji 0 1 2 3 4 5 6 70 11 1 1

그림으로 나타내면 다음과 같은 과정으로 갱신되던 것을(0, 0) (0. 3) (1, 1)(0, 0) (0. 9) (1, 4)0 3 6 9 12 151 4 7 10 13 162 5 8 11 14 17

0 3 6 9 12 151 4 7 10 13 162 5 8 11 14 17

0 3 6 9 12 151 4 7 10 13 162 5 8 11 14 17

초기상태 0000(0) 세로로 놓을 경우 0011(3) 가로로 놓을 경우 1001(9)0 3 6 9 12 151 4 7 10 13 162 5 8 11 14 17

0 3 6 9 12 151 4 7 10 13 162 5 8 11 14 17

을 중심으로1 0001(1) 을 중시믕로1 0100(4)

중간과정을 생략하고 아래 그림과 같이 에서 과 를 직접 갱신하도록(0, 0) (1, 1) (1, 4)하는 것이다.

동적계획법 (Dynamic Programming)

- 25 -

0 3 6 9 12 151 4 7 10 13 162 5 8 11 14 17

0 3 6 9 12 151 4 7 10 13 162 5 8 11 14 17

0 3 6 9 12 151 4 7 10 13 162 5 8 11 14 17

초기상태을 중심으로0 000(0)

세로로 놓을 경우을 중심으로1 001(1)

가로로 놓을 경우을 중심으로1 100(4)

이렇게 하면 번은 현재의 상태에서 관리할 필요가 없으므로 테이블의 크기는i+3 23으로도 처리가 가능하다.

이러한 방법으로 다음 단계를 진행해 보자.가 일 경우 은 번이 이미 채워져 있으므로 다시 채울 필요없이 이 갱신i 1 (1, 1) 1 (2, 0)이 가능하다.그리고 는 세로로 놓을 경우 번과 번을 채우는 것이므로 번의 입장에서 생각(1, 4) 1 2 2하면 번 은 번 은 번 은 이므로 을 갱신하면 된다4 (i+2) 0, 3 (i+1) 1, 2 (i) 1 (2, 3) .가로로 놓을 경우는 번 은 번 은 번 은 이므로 이 갱신된다4 (i+2) 1, 3 (i+1) 1, 2 (i) 0 (2, 6) .

ji 0 1 2 3 4 5 6 70 11 1 12 1 1 1

다음은 가 일 경우 에서 가로로 놓을 경우 번과 번이 채워지고 번의 입장에i 2 (2, 0) 2 5 3서 보면 번 은 번 은 번 은 이므로 가 갱신된다5 (i+2) 1, 4 (i+1) 0, 3 (i) 0 (3, 4) .그리고 마지막 행이므로 세로로는 놓을 수 없다.

은 번이 이미 채워져 있으므로 을 갱신하게 된다(2, 3) 2 (3, 1) .은 가로로 채울 경우 번과 번이 채워져서 번의 입장에서 번 번 번이 모(2, 6) 2 5 3 5 , 4 , 3

두 이므로 을 갱신한다1 (3, 7) .ji 0 1 2 3 4 5 6 7

0 11 1 12 1 1 13 1 1 1

- 26 -

이렇게 하여 까지 갱신을 하게 되면 아래와 같은 테이블이 완성된다12 .이 라면 최종적으로 또는 에 구해진 결과를 출력하면 된다n 4 D[11][1] D[12][0] .

ji 0 1 2 3 4 5 6 70 11 1 12 1 1 13 1 1 14 1 2 15 3 1 16 3 1 17 4 3 18 4 4 39 4 4 310 4 7 411 11 4 412 11 4 4

동적계획법 (Dynamic Programming)

- 27 -

문제 여러 줄로 타일 깔기 (1442)

너비 높이 의 직사각형을 길이 높이 의 직사각형으로 빈칸 없이 채우려고 한N, M 2, 1다 채우는 방법은 아래 그림과 같다. .

[ ]입력은 한 줄로 이뤄지며 길이 과 높이 이 공백을 사이에 두고 입력된다N M .(1 N, M 11).

[ ]입력에 대해 해당 직사각형을 채우는 가능한 모든 경우의 수를 출력한다.

1 11 4 1

2 22 4 5

3 32 11 144

- 28 -

풀이

세줄 타일깔기를 응용하면 된다.세줄 타일깔기의 경우 테이블의 크기는 D[N * 3][23+1 이 필요했다면]이 문제에서는 D[N * M][2M+1 이 필요하다] .

그리고 세줄깔기에서 타일을 가로로 채울 경우 번은i + 3 23이고 번은 이므로 를 더i 1 9한 위치를 갱신했던 것을 2M 을 갱신하는 것으로 바꿔주면 된다+ 1 .

그리고 일 경우 맨 아래행을 나타내던 것을 로 수정하i % 3 == 2 i % M == M - 1면 된다.

점화식은 다음과 같다.이전은 위치는 모두 채워지고 번을 기준으로 상태인 경우의 수D[i][j] = i i j

m = 1 << Mif(j & 1) D[i + 1][j>>1] += D[i][j]else D[i][j + m + 1] += D[i][j]if(i % M < M - 1 && (j & 3)==0) D[i][j + 3] += D[i][j]

최종적으로 을 출력하면 된다D[N * M][0] .

동적계획법 (Dynamic Programming)

- 29 -

문제 두부 모판 자르기 (1993)

두부 공장에서 만들어내는 크기가 인 두부 모판이 있다 이 모판을KOI NxN(N 11) .크기의 단위두부가 개 붙어있는 형태의 포장단위 즉 혹은 크기 로 잘1x1 2 ( , 1x2 2x1 )

라서 판매한다 그런데 두부제조 공정상 모판에 있는 각 단위두부의 품질은. A, B, C,급으로 분류되고 잘려진 포장단위의 두부 가격은 이 포장단위에 있는 두 개의 단위F ,두부의 품질에 따라서 그림 과 같이 정해진다1 .

포장단위에 있는 두 단위두부가 급이면 원을 받고 급이면 원을[A,A] 100 , [A,B] 70 ,급이면 원을 급이면 원을 급이면 원을 급이면 원을[A,C] 40 , [B,B] 50 , [B,C] 30 , [C,C] 20

받는다 포장단위에 있는 두 개의 단위두부 중 하나라도 급이 있으면 이 포장단위는. F한푼도 받을 수 없다 두부 모판의 품질이 주어질 때 가장 높은 가격을 받도록. NxN ,두부 모판을 혹은 크기의 포장단위들로 자르고자 한다 예를 들어 그림 와1x2 2x1 . 2같은 두부 모판이 주어져 있다고 하자3x3 .

이 경우 그림 과 같이 자르면 개의 포장단위가 만들어진다, 3 4 .

- 30 -

이때 이들 포장단위의 가격은 그리고, [A,A]=100, [F,C]=0, [A,C]=40, [A,B]=70이다 여기서 오른쪽 위 와 같이 단위두부 하나는 포장단위가 아니므로 판매할 수. [C]없다 따라서 총 가격은 원이 된다 이 가격은 그림 와 같은 두부모판에서. 210 . 2 3x3받을 수 있는 가장 높은 가격이다.

두부모판의 크기와 단위두부의 등급이 주어질 때 이를 포장단위로 잘라 받을 수 있는,총 가격의 최댓값을 구하는 프로그램을 작성하시오 부분 점수는 없다. .

[ ]첫째 줄에는 두부모판의 크기를 나타내는 이 주어진다N(2 N 11) .둘째 줄부터 줄에 걸쳐 각 줄에 두부모판의 단위두부의 등급들이 행단위로 등급사N이에 공백 없이 첫 번째 행부터 차례로 주어진다.

[ ]입력된 두부모판을 포장단위로 잘라서 받을 수 있는 최대 가격을 첫째 줄에 출력한다.

3AACFCAACB

210

동적계획법 (Dynamic Programming)

- 31 -

풀이

타일깔기를 응용하면 된다.타일깔기는 경우의 수를 구하는 것이므로 현재의 값을 다음 갱신되는 위치에 누적해 주면 되지만 이 문제에서는 다음 갱신되는 위치의 값과 비교해서 더 좋은 경우 갱신해 주면 된다.

또 한가지 타일깔기는 모든 위치를 채워야 하므로 현재위치가 반드시 채워져야만 다음위치를 갱신할 수 있지만 이 문제의 경우에는 현재의 위치를 사용하지 않아도 되므로 j가 홀수인 경우뿐 아니라 짝수인 경우에도 현재의 값으로 다음위치를 갱신할 수 있다.

이전까지의 두부와 를 기준으로 상태의 두부를 사용하여 만들 수 있는 최D[i][j] = i i j대가치 이라 하면 다음과 같은 점화식을 얻을 수 있다, m = 1<<N .

D[i + 1][j>>1] = Max(D[i + 1][j>>1], D[i][j]);if((j & 1)==0)

번과 번으로 포장하는 가격D[i][j + m + 1] = D[i][j] + i (i + N) )if(i % N < N - 1 && (j & 3) == 0)

번과 번으로 포장하는 가격D[i][j + 3] = D[i][j] + i (i + 1)

입력받은 문자는 번위치에 숫자로 변환하여 저장하고 처리하면 코딩이 수월해 질 수 있i다.

최종적으로 을 출력하면 된다D[N * N][0] .

- 32 -

문제 건물 세우기 (1249)

주 정올에서는 여러 개의 빌딩을 새로 지을 계획이다 그래서 빌딩을 세울 장소를( ) .선정하였다 그리고 각 빌딩을 각 장소에 세울 경우에 드는 비용을 추정하였다 예를. .들어서 아래의 표를 보자.

1 2 3A 4 7 3B 2 6 1C 3 9 4

는 건물을 나타내고 은 장소를 나타낸다 예를 들어서 건물 를 장소A, B, C , 1, 2, 3 . B에 세우면 비용이 가 들고 장소 에 세우면 비용이 장소 에 세우면 비용이1 2 , 2 6, 3 1만큼 든다 물론 한 장소에는 한 건물밖에 세울 수 없다 만일 를 장소 에 를 장. . A 2 , B소 에 를 에 세우면 전체 비용이 이 필요하다 그런데 를3 , C 1 7 + 1 + 3 = 11 . A 3,를 를 에 세우면 가 필요하다B 1, C 2 3 + 2 + 9 = 14 .

각 빌딩을 어느 장소에 세우면 비용의 합이 최소가 되는지 구하는 프로그램을 작성하시오.

[ ]입력 파일의 첫 줄은 빌딩의 개수 이 들어있다n(1 n 20) .그 다음 줄에는 각 건물을 각 장소에 세울 경우에 드는 비용이 입력된다 물론 각n .줄 에는 개의 수가 입력된다n .비용을 나타내는 수의 범위는 이상 미만이다1 100 .

[ ]첫 줄에는 최소비용을 출력한다.둘째 줄에는 건물을 세울 장소들을 알파벳 순서에 따라 빈칸을 하나씩 두고 출력한다.

동적계획법 (Dynamic Programming)

- 33 -

411 12 18 4014 15 13 2211 17 19 2317 14 20 28

611 3 4 2

- 34 -

풀이

가장 간단하게는 모든 경우를 조합해보는 백트래킹 알고리즘을 생각해 볼 수 있다.첫 번째 건물부터 모든 장소를 차례대로 선택하고 두 번째 건물은 첫 번째 건물이 선택,된 장소를 제외한 모든 장소를 선택하고 이렇게 차례대로 선택한 후 얻은 비용중 최소,값을 출력하면 된다.이 경우 시간복잡도는 이 된다 따라서 이 이내라면 백트래킹 알고리즘으로O(N!) . N 12충분히 결과를 알아낼 수 있지만 그 이상의 경우에는 시간내에 결과를 보장받을 수 없다.

첫 번째 건물은 번 장소 두 번째 건물은 번 장소에 세우는 경우와 첫 번째 건물은1 , 2 , 2번장소 두 번째 건물은 번 장소에 세우는 경우라면 어차피 첫 번째와 두 번째 건물을, 1번과 번 장소에 세운 것이므로 그 두 가지를 각각 진행할 필요가 없이 두 가지중 비1 2용이 더 작은 것으로만 다음 단계로 진행하면 된다.

이와 같이 순서는 다르더라도 같은 건물이 선택된 경우라면 그 중 가장 좋은 값만으로다음단계를 진행하도록 한다면 불필요한 반복을 제거할 수 있다.현재까지 선택된 장소는 이진수를 이용하여 비트형식으로 나타낼 수 있다.

문제에서 주어진 예를 가지고 해결하는 과정을 살펴보도록 하자.은 이므로 장소가 선택되는 모든 경우의 수는 아래와 같이 가지가 된다N 3 8 .

상태의 최소비용D[i] = i마지막으로 세워진 건물의 장소를 표시하도록 한다P[i] = .

최소비용을 구하는 것이므로 초기 상태는 건물을 한 개도 세우지 않은 상태 즉 D[0] =이 되고 나머지는 모두 최대값이 되어야 한다0 .

동적계획법 (Dynamic Programming)

- 35 -

배열번호진수(10 )

진수2표시

선택된 장소 처리된건물의 수 D[i] P[i]번3 번2 번1

0 000 0 0 0 0 0 01 001 0 0 1 1 ∞

2 010 0 1 0 1 ∞

3 011 0 1 1 2 ∞

4 100 1 0 0 1 ∞

5 101 1 0 1 2 ∞

6 110 1 1 0 2 ∞

7 111 1 1 1 3 ∞

처음은 값이 확정된 에서부터 다음 단계를 진행해보자0 .에는 건물이 하나도 세워지지 않았으므로 첫 번째 건물을 세우는 경우를 모두 생각해0보자.번에 건물을 세운다면 즉 이되고 가 된다1 001 1 D[1] = 4 .번에 건물을 세우면 즉 가 되고 이 된다2 010 2 D[2] = 7 .번에 건물을 세우면 즉 가 되고 이 된다3 100 4 D[4] = 3 .갱신한 내용을 살펴보면 다음과 같다.

배열번호진수(10 )

진수2표시

선택된 장소 처리된건물의 수 D[i] P[i]번3 번2 번1

0 000 0 0 0 0 0 01 001 0 0 1 1 4 12 010 0 1 0 1 7 23 011 0 1 1 2 ∞

4 100 1 0 0 1 3 35 101 1 0 1 2 ∞

6 110 1 1 0 2 ∞

7 111 1 1 1 3 ∞

이번에는 번을 기준으로 다음 단계를 진행해보자1 .번에는 현재 건물이 번 장소에 세워진 것이므로 건물을 번이나 번에 세울 수 있1 A 1 B 2 3다.건물을 번에 세울 경우는 이 되므로 건물을 번 장소에B 2 011 D[3] = 4(D[1]) + 6(B 2세우는 비용 이 된다) = 10 .번에 세울 경우는 이 되므로 가 된다3 101 D[5] = 4 + 1 = 5 .

- 36 -

배열번호진수(10 )

진수2표시 선택된 장소 처리된

건물의 수D[i]최소비(용)

P[i]번3 번2 번1

0 000 0 0 0 0 0 01 001 0 0 1 1 4 12 010 0 1 0 1 7 23 011 0 1 1 2 10 24 100 1 0 0 1 3 35 101 1 0 1 2 5 36 110 1 1 0 2 ∞

7 111 1 1 1 3 ∞

이번에는 번을 기준으로 다음 단계를 진행해보자2 .번에는 현재 건물이 번 장소에 세워진 것이므로 건물을 번이나 번에 세울 수 있1 A 2 B 1 3다.건물을 번에 세울 경우는 이 되므로 건물을 번 장소에B 1 011 D[3] = 7(D[2]) + 2(B 1세우는 비용 가 되고 기존에 보다 비용이 작으므로 갱신해준다) = 9 10 .번에 세울 경우는 이 되므로 이 된다3 110 D[6] = 7 + 1 = 8 .

배열번호진수(10 )

진수2표시

선택된 장소 처리된건물의 수 D[i] P[i]번3 번2 번1

0 000 0 0 0 0 0 01 001 0 0 1 1 4 12 010 0 1 0 1 7 23 011 0 1 1 2 9 14 100 1 0 0 1 3 35 101 1 0 1 2 5 36 110 1 1 0 2 8 37 111 1 1 1 3 ∞

이번에는 번을 기준으로 다음 단계를 진행해보자3 .번에는 현재 건물과 건물이 번과 번 장소에 세워진 것이므로 건물을 번에 세울3 A B 1 2 C 3수 있다 이 경우 이 되므로 건물을 번에 세우는 경우. 111 D[7] = 9(D[3]) + 4(C 3 ) =이 된다13 .

동적계획법 (Dynamic Programming)

- 37 -

배열번호진수(10 )

진수2표시

선택된 장소 처리된건물의 수 D[i] P[i]번3 번2 번1

0 000 0 0 0 0 0 01 001 0 0 1 1 4 12 010 0 1 0 1 7 23 011 0 1 1 2 9 14 100 1 0 0 1 3 35 101 1 0 1 2 5 36 110 1 1 0 2 8 37 111 1 1 1 3 13 3

이번에는 번을 기준으로 다음 단계를 진행해보자4 .번에는 현재 건물이 번 장소에 세워진 것이므로 건물을 번이나 번에 세울 수 있4 A 3 B 1 2다.건물을 번에 세울 경우는 이 되므로 건물을 번 장소에B 1 101 D[5] = 3(D[4]) + 2(B 1세우는 비용 가 되고 기존에 와 같으므로 갱신할 필요가 없다) = 5 5 .번에 세울 경우는 이 되므로 가 되고 기존에 보다 크기 때문2 110 D[6] = 3 + 6 = 9 8에 갱신할 필요가 없다.

이번에는 를 기준으로 진행해 보자5 .번에는 현재 건물과 건물이 번과 번 장소에 세워진 것이므로 건물을 번에 세울5 A B 1 3 C 2수 있다 이 경우 이 되므로 건물을 번에 세우는 경우. 111 D[7] = 5(D[5]) + 9(C 2 ) =이 되고 기존에 보다 크기 때문에 갱신할 필요가 없다14 13 .

이번에는 을 기준으로 진행해 보자6 .번에는 현재 건물과 건물이 번과 번 장소에 세워진 것이므로 건물을 번에 세울6 A B 2 3 C 1수 있다 이 경우 이 되므로 건물을 번에 세우는 경우. 111 D[7] = 8(D[6]) + 3(C 1 ) =이 되고 기존에 보다 작기 때문에 갱신해 준다11 13 .

- 38 -

배열번호진수(10 )

진수2표시

선택된 장소 처리된건물의 수 D[i] P[i]번3 번2 번1

0 000 0 0 0 0 0 01 001 0 0 1 1 4 12 010 0 1 0 1 7 23 011 0 1 1 2 9 14 100 1 0 0 1 3 35 101 1 0 1 2 5 36 110 1 1 0 2 8 37 111 1 1 1 3 11 1

세 건물을 모두 세운 경우는 즉 배열 이므로 에 있는 이 최소 비용이 되고111 7 D[7] 11세 번째 건물 을 세운 곳은 에 있는 번 장소이다(C) P[7} 1 .

에서 번 장소를 빼면 이 되므로 번에 가서 보면 두 번째 건물 가 세워진 위111 1 110 6 (B)치는 의 번 장소이다P[6] 3 .

에서 번 장소를 빼면 이 되므로 번에 가서 보면 첫 번째 건물 가 세워진 위110 3 010 2 (A)치가 의 번 장소이다P[2] 2 .

동적계획법 (Dynamic Programming)

- 39 -

문제 해밀턴 순환회로2 (1545)

태현이는 방학기간 동안 택배 알바를 해서 최고급 노트북을 장만하려고 한다.오늘 배달해야 하는 장소를 한 번씩만 방문해서 물건을 모두 배달하고 다시 회사로돌아와야 한다 배달하는 장소에만 도착할 수 있다면 물건은 모두 배달할 수 있으므로.물건의 개수나 크기등은 고려하지 않아도 된다.그런데 문제는 방문하는 순서를 어떻게 정할지가 고민이다 어떤 장소에서 다른 장소.로 이동하는 데에는 비용이 발생하는데 만약 방문하는 순서를 잘못 정하게 되면 알바비로 받은 돈을 모두 이동비용으로 사용하고 눈물을 흘릴지도 모른다.태현이가 물건을 모두 배달하고 회사로 돌아오기 위한 최소의 비용을 계산하는 프로그램을 작성해 주자.

[ ]첫 줄은 배달해야 하는 장소의 수 이 주어진다 이때 출발지 회사 는N(1 N 19) . , ( ) 1번이다 둘째 줄은 크기의 장소와 장소를 이동하는 비용 비용 이. N X N (0 100)한 칸씩의 공백으로 구분하여 주어진다 비용은 양방향이 아니라 단방향으로 가기 위.한 비용이다 예들 들어 첫 번째 행 두 번째 열에 적힌 비용은 번 장소에서 번 장소. 1 2로 이동하기 위한 비용을 의미하며 번 장소에서 번 장소로 이동하기 위한 비용은, 2 1두 번째 행 첫 번째 열에 주어진다.장소 사이에 이동할 수 있는 방법이 없다면 비용을 으로 표시한다0 .

[ ]회사에서 출발하여 오늘 배달해야 하는 모든 장소를 한 번씩 들러 물건을 배달하고회사에 돌아오기 위한 최소의 비용을 출력한다.

50 14 4 10 2014 0 7 8 74 5 0 7 1611 7 9 0 218 7 17 4 0

30

- 40 -

풀이

이 문제 역시 기본적으로 백트래킹으로 해결이 가능하지만 시간복잡도가 이기 때O(N!)문에 이 이상일 경우 시간이 초과될 가능성이 높다N 13 .

또 다른 해결방법은 브랜치 앤 바운드 알고리즘으로 현재 상태에서(Branch & Bound)앞으로 남은 방문지들까지의 최소비용을 더한 결과를 기준으로 가지치기를 잘 하게 되면 시간내에 해결이 가능하다.

이제부터 비트다이나믹을 이용한 풀이를 살펴보도록 하자.메모리를 최대한 활용하기 위해서 도시번호를 까지로 하고 이 경우 출발지는0 ~ N-1이다0 .

현재까지의 방문상태가 이고 현재위치가 인 경우의 최소비용으로 정의하고D[i][j] = i j출발지가 번이므로 초기값은 이 되고 나머지는 최대값이 된다0 D[1][0] = 0 .

로 오기 직전의 방문도시가 라면j kD[i][j] = D[i - 2j 가 된다][k] + cost[k][j] .

또는 현재의 도시에서 다음 방문 도시 까지의 비용을 구하는 방법은(k)D[i+2k 가 된다][k] = D[i][j] + cost[j][k] .

모든 장소를 방문한 경우는 이므로 최종 위치의 도시에서 출발도시 로(1<<N) - 1 (0)돌아가기 위한 비용을 더한 최소값인 을 출력하면min(D[(1<<N)-1][i] + cost[i][0])된다.

동적계획법 (Dynamic Programming)

- 41 -

문제 교통수단 선택하기 (3008)

정올이는 정보과학 교육과 관련된 세미나에 초대를 받아 참석하려고 한다.

세미나가 열리는 도시에 가기 위해서는 여러 도시를 거쳐서 이동할 수 있으며 도시간이동하기 위한 교통수단은 초고속열차 렌트카 자전거를 이용하는 가지 방법이 있다, , 3 .정올이는 늘 접이식 자전거를 들고 다니기 때문에 자전거로 이동하는 데에는 비용이발생하지 않는다 접이식 자전거는 당연히 열차와 렌트카에도 싣고 탈 수 있다. .정올이가 이용하는 렌트카 회사는 여러 도시에 지점이 있어서 지점이 있는 도시에서는 어디에서든 빌릴 수도 있고 반납할 수도 있다.

자전거를 제외한 교통수단에 따른 비용은 단위 거리당 금액으로 정해진다.모든 연결된 도로에는 렌트카 및 자전거로 이동하는 것은 물론 초고속열차도 운행하고 있어서 모두 이용이 가능하다.세미나가 열리는 도시에는 세미나를 시작하기 전까지만 도착하면 된다.물론 렌트카가 있다면 세미나장에 도착하기 전에 반드시 반납을 해야 한다.

각 도시 및 교통수단에 관한 정보가 주어질 때 정올이가 세미나가 시작하기 전까지세미나 개최 도시에 도착하기 위한 최소비용을 구하는 프로그램을 작성하라.

[ ]입력의 첫 번째 줄에는 도시의 수 과 간선 도로 의 수 세미나 개최까지 남은시간N ( ) M,렌트카 지점의 수 이 차례로 주어진다 도시의 번호는 부터 까지 부여되고 정T, L . 1 N

올이가 출발하는 도시는 번이며 세미나가 개최되는 도시는 번이다1 N .(2<= N <= 100, N <= M <= N * (N - 1) / 2, 2 <= T <= 1000, 1 <= L<= N))

두 번째 줄부터 줄에 걸쳐 개의 정수 가 주어지는데 이는 도시와 도시M 3 s, e, d s e사이의 거리가 임을 나타낸다 도로간 양방향 통행이 가능하다d . .(1 <= s, e <= N, 1 <= d <= 20)

- 42 -

다음 줄에는 렌트카 지점이 있는 도시의 번호 개가 공백으로 구분하여 주어진다L .

다음 줄에는 자전거 렌트카 고속열차 순으로 단위 거리를 이동하는데 걸리는 시간이, ,차례대로 주어진다 자전거 렌트카 고속열차. (30 >= > > >= 1)

마지막 줄에는 렌트카와 고속열차의 순서대로 단위 거리를 이동하는데 드는 비용이주어진다 자전거는 비용이 발생하지 않으므로 입력되지 않는다. .

렌트카 고속열차(1 <= < <=3000)

[ ]세미나가 시작되기 전까지 도착하기 위한 최소비용을 출력한다.

4 6 100 21 2 31 3 41 4 72 3 32 4 53 4 22 330 10 3200 1000

3600

동적계획법 (Dynamic Programming)

- 43 -

풀이우선순위 큐 를 이용하여 비용이 최소인 자료부터 꺼내서 다음단계를 진(Priority Queue)행해 나가는 방법으로 를 운영하다가 큐에서 꺼낸 자료가 목적지일 때 비용을 출력BFS하면 된다.

예제의 경우를 살펴보자.노드 시간 렌트카유무 비용 를 원소로 하고 비용을 기준으로 을 만든다( , , , ) max_heap .초기값 을 넣는다 번노드에 시간에 렌트카없이 도착하는 비용이 원(1, 0, 0, 0) . (1 0 0 )

번노드에서 갈수 있는 곳은 번이다1 2, 3, 4 .먼저 번으로 이동하는 경우를 갱신해 보자2 .자전거로 가는 경우 시간은 이고 비용은 이므로30 * 3 = 90 0 (2, 90, 0, 0)택시로 가는 경우 시간은 비용은 이므로3 * 3 = 9 3 * 1000 = 3000 (2, 9, 0, 3000)을 자료에 추가한다.

다음은 번 노드로 이동하는 경우 과 을 추가하고3 (3, 120, 0, 0) (3, 12, 0, 4000)번으로 이동하는 경우 과 을 추가해야 하지만4 (4, 210, 0, 0) (4, 21, 0, 7000) (4, 210,의 경우는 이미 시간이 을 넘었기 때문에 제외한다0, 0) 200(T) .

다음은 현재 자료에서 비용이 최소인 으로 갱신해 보자(2, 90, 0, 0) .먼저 번 도시는 렌트카가 있으므로 을 추가할 수 있다2 (2, 90, 1, 0) .그리고 번으로 이동할 경우 과 을 추가한다3 (3, 180, 0, 0) (3, 99, 0, 3000) .

같은 방법으로 비용이 낮은 것부터 에서 꺼내어 다음 단계를 갱신해 나가다가heap heap에서 꺼낸 자료의 노드가 목적지라면 그 자료의 비용이 목적지에 도착하기 위한 최소비용이 된다.

- 44 -

- 45 -

Chapter 2소스코딩집( )

.

.

,.

.

- 46 -

소스 코딩 차례( )

정렬 (SORT) (1972) 48힙정렬 (Hesp_Sort) (2082) 49못생긴 수 (1318) 51미로탈출 게임 (3010) 53세줄로 타일깔기 (2112) 55여러 줄로 타일 깔기 (1442) 59두부 모판 자르기 (1993) 60건물 세우기 (1249) 61해밀턴 순환회로2 (1545) 64교통수단 선택하기 (3008) 65

- 48 -

문제 정렬 (SORT) (1972)

#include <stdio.h>

int N, C, a[100010], b[100010];

void m_sort(int s, int e){if(s >= e) return;int m, k=s, l, r, i;m = (s + e) / 2;l = s, r = m + 1;m_sort(s, m);m_sort(m + 1, e);while (l <= m && r <= e) b[k++] = (a[l] < a[r]) ? a[l++] : a[r++];while (l <= m) b[k++] = a[l++];while (r <= e) b[k++] = a[r++];for (i = s; i <= e; i++) a[i] = b[i];

}

int main(){int i;scanf("%d %d", &N, &C);for (i = 1; i <= N; i++) scanf("%d", &a[i]);

m_sort(1, N);

if (C == 0) {for (i = 1; i <= N; i++) printf("%d\n", a[i]);

}else {for (i = N; i >= 1; i--) printf("%d\n", a[i]);

}return 0;

}

동적계획법 (Dynamic Programming)

- 49 -

문제 힙정렬 (Hesp_Sort) (2082)

#include <stdio.h>#define SWAP(x, y) {int z = x; x = y; y = z;}

int heap[500010];int N;

void push(int p){if (p <= 1 || heap[p / 2] > heap[p]) return;SWAP(heap[p / 2], heap[p]);push(p / 2);

}

void pop(int p, int n){int np = p * 2;if (np < n && heap[np] < heap[np + 1]) np++;if (np > n || heap[p] >= heap[np]) return;SWAP(heap[p], heap[np]);pop(np, n);

}

void output(){for(int i = 1; i <= N; i++) {printf("%d ", heap[i]);

}printf("\n");

}

algorithm

- 50 -

int main(){int i;scanf("%d", &N);for (i = 1; i <= N; i++) {scanf("%d", &heap[i]);push(i);

}output();for (i = N; i > 1; i--) {SWAP(heap[1], heap[i]);pop(1, i - 1);

}output();return 0;

}

동적계획법 (Dynamic Programming)

- 51 -

문제 못생긴 수 (1318)

#include <stdio.h>#define swap(x, y) {long long z=x; x=y; y=z;}

long long heap[5000], n;

void push(long long p){if(p <= 1 || heap[p / 2] <= heap[p]) return;swap(heap[p / 2], heap[p]);push(p / 2);

}

void pop(long long p){long long np = p * 2;if(np < n && heap[np] > heap[np + 1]) np++;if(np > n || heap[p] <= heap[np]) return;swap(heap[p], heap[np]);pop(np);

}

algorithm

- 52 -

long long out(long long num){long long cnt = 0, ans = 0;n = 0;heap[1] = 1;while(cnt < num) {while(ans == heap[1]) {heap[1] = heap[n--];pop(1);

}ans = heap[1];cnt++;heap[++n] = ans * 2; push(n);heap[++n] = ans * 3; push(n);heap[++n] = ans * 5; push(n);

}return ans;

}

int main(){long long num;while(1) {scanf("%lld", &num);if(num == 0) break;printf("%lld\n", out(num));

}return 0;

}

동적계획법 (Dynamic Programming)

- 53 -

문제 미로탈출 게임 (3010)

#include <stdio.h>

int N, M, L, a[110][110], chk[110][110][5], visit[110][110];int xx[5] = {0, 0, 0, 1, -1}, yy[5] = {0, 1, -1, 0, 0};

struct data {int x, y, p, cnt;

} Q[60000];

void dfs(int x, int y, int d, int cnt){if(!a[x][y] || visit[x][y]) return;if (chk[x][y][d] <= cnt || chk[N][M][L] <= cnt + N + M - x - y) return;visit[x][y] = 1;chk[x][y][d] = cnt;

int nx = x + xx[d], ny = y + yy[d];if(a[nx][ny] < 3) dfs(nx, ny, d, cnt + 1);else if (d < 3) {dfs(nx, ny, 3, cnt + 1);dfs(nx, ny, 4, cnt + 1);

}else {dfs(nx, ny, 1, cnt + 1);dfs(nx, ny, 2, cnt + 1);

}visit[x][y] = 0;

}

algorithm

- 54 -

int main(){int i, j, k;scanf("%d %d", &N, &M);for (i = 1; i <= N; i++) {for (j = 1; j <= M; j++) {scanf("%d", &a[i][j]);for (k = 1; k <= 4; k++) chk[i][j][k] = 54321;

}}if(a[N][M]==1 || a[N][M]==6) L = 3;else L = 1;if(a[1][1]==1 || a[1][1]==6) dfs(1,1,3,1);else dfs(1,1,1,1);

if (chk[N][M][L]==54321) printf("0\n");else printf("%d\n", chk[N][M][L]);return 0;

}

동적계획법 (Dynamic Programming)

- 55 -

문제 세줄로 타일깔기 (2112)

각 위치마다 모양별 경우의 수 저장 번 위치에 개 채워진 경우의 수// , D[i][j] = i j

#include <stdio.h>

int N;int D[35][4];

int main(){

int i;scanf("%d", &N);

D[0][3] = 1;

for (i = 1; i <= N; i++) {D[i][1] = D[i - 1][2];D[i][2] = D[i - 1][1] + D[i - 1][3] * 2;D[i][3] = D[i][1] + D[i - 2][3];

}

printf("%d", D[N][3]);}

algorithm

- 56 -

각 위치마다 완전하게 채워진 경우의 수 저장//

#include <stdio.h>

int N, D[100] = { 1, }, S[100] = { 1, };

int main() {scanf("%d", &N);for (int i = 2; i < 100; i += 2){

D[i] = D[i - 2] + S[i - 2] * 2;S[i] = S[i - 2] + D[i];

}printf("%d", D[N]);

return 0;}

동적계획법 (Dynamic Programming)

- 57 -

비트 다이나믹 현재위치를 중심으로 갱신// -

#include <stdio.h>

int n;int d[100][16] = {{1}};

int main(){int i, j;

scanf("%d", &n);

for (i = 0; i < n * 3; i++) {for (j = 0; j <= 15; j++) {if (!d[i][j]) continue;if (j & 1) d[i + 1][j / 2] += d[i][j];else d[i][j + 9] += d[i][j];if(i % 3 < 2 && !(j & 3)) d[i][j + 3] += d[i][j];

}}printf("%d\n", d[n * 3][0]);

return 0;}

algorithm

- 58 -

비트 다이나믹 다음 위치를 직접 갱신// -

#include <stdio.h>

int N, D[100][8] = {{0}};

int main(){

int i, j;scanf("%d", &N);

for (i = 0; i <= N * 3; i++) {for (j = 0; j < 8; j++) {

if (D[i][j] == 0) continue;if (j % 2 == 1) D[i + 1][j / 2] += D[i][j];else D[i + 1][j / 2 + 4] += D[i][j];if (i % 3 < 2 && (j & 3) == 0) D[i + 1][j / 2 + 1] += D[i][j];

}}

printf("%d\n", D[N * 3][0]);

return 0;}

동적계획법 (Dynamic Programming)

- 59 -

문제 여러 줄로 타일 깔기 (1442)

#include <stdio.h>

int N, M;long long D[150][1<<12];

int main(){int i, j, m;scanf("%d %d", &N, &M);m = 1<<M;D[0][0]=1;for (i = 0; i < N * M; i++) {for (j = 0; j < m * 2; j++) {if(D[i][j]==0) continue;if(j & 1) D[i + 1][j>>1] += D[i][j];else D[i][j + m + 1]+=D[i][j];if(i % M < M - 1 && (j & 3)==0) D[i][j + 3] += D[i][j];

}}printf("%lld\n", D[N * M][0]);return 0;

}

algorithm

- 60 -

문제 두부 모판 자르기 (1993)

#include <stdio.h>

int N, D[150][1<<12], ans;int a[150];int score[6][6] = {{100, 70, 40},{70, 50, 30},{40, 30, 20}};int Max(int x, int y) {return x > y ? x : y;}int main(){int i, j, m;char tmp;scanf("%d", &N);m = 1<<N;for (i = 0; i < N * N; i++) {scanf(" %c", &tmp);a[i] = tmp - 'A';

}

for (i = 0; i < N * N; i++) {for (j = 0; j < m * 2; j++) {D[i + 1][j>>1] = Max(D[i + 1][j>>1], D[i][j]);if((j & 1)==0)

D[i][j + m + 1] = Max(D[i][j + m + 1],D[i][j] + score[a[i]][a[i + N]]);

if(i % N < N - 1 && (j & 3) == 0)D[i][j + 3] = Max(D[i][j + 3], D[i][j] + score[a[i]][a[i + 1]]);

}}

printf("%d\n", D[N * N][0]);return 0;

}

동적계획법 (Dynamic Programming)

- 61 -

문제 건물 세우기 (1249)

상향식//

#include <stdio.h>

int N, A[22][22], bn, D[1 << 20], P[1 << 20], cnt[1 << 20];

void dy(){

int i, j, k, t;초기값 최대for (i = 1; i<bn; i++) D[i] = 54321; //

for (i = 0; i<bn - 1; i++) {다음번 건물의 개수k = cnt[i] + 1; //

건물번호 건물for (j = 1, t = 1; j <= N; j++, t <<= 1) { //j= , t= bitif ((i&t) == 0 && D[i + t] > D[i] + A[k][j]) {

건물의 개수cnt[i + t] = k; //D[i + t] = D[i] + A[k][j];

건물번호P[i + t] = j; //}

}}

}

void track(int n){

if (!n) return;track(n - (1 << (P[n] - 1)));printf("%d ", P[n]);

}

algorithm

- 62 -

int main(){

int i, j;scanf("%d", &N);bn = 1 << N; //2 ^ Nfor (i = 1; i <= N; i++) {

for (j = 1; j <= N; j++) {scanf("%d", &A[i][j]);

}}dy();

printf("%d\n", D[bn - 1]);track(bn - 1);return 0;

}

동적계획법 (Dynamic Programming)

- 63 -

메모이제이션//#include <stdio.h>int N, A[22][22], bn, D[1 << 20], P[1 << 20], cnt[1 << 20];

int dy(int n, int cnt) {int i, j;if (n == 0 || D[n]) return D[n];D[n] = 54321;

건물번호 건물for (i = 1, j = 1; i <= N; i++, j <<= 1) { // i = , j = bitif ((n & j) && D[n] > dy(n - j, cnt - 1) + A[cnt][i]) {

D[n] = D[n - j] + A[cnt][i];P[n] = i;

}}return D[n];

}

void track(int n) {if (!n) return;track(n - (1 << (P[n] - 1)));printf("%d ", P[n]);

}

int main(){

int i, j;scanf("%d", &N);bn = 1 << N; //2 ^ Nfor (i = 1; i <= N; i++) {

for (j = 1; j <= N; j++) {scanf("%d", &A[i][j]);

}}printf("%d\n", dy(bn - 1, N));track(bn - 1);return 0;

}

algorithm

- 64 -

문제 해밀턴 순환회로2 (1545)

#include <stdio.h>#define INF 987654321int n, a[25][25], D[1<<19][20];int Min(int x, int y) { return x < y ? x : y; }

int main(){int i, j, k, l, m, ans = INF;scanf("%d", &n);m = (1 << n);for (i = 0; i < n; i++) {for (j = 0; j < n; j++) {scanf("%d", a[i] + j);if (!a[i][j]) a[i][j] = INF;

}}

for (i = 0; i < m; i++) for (j = 0; j < n; j++) D[i][j] = INF;D[1][0] = 0;for (i = 1; i < m - 1; i+=2) {for (j = 0; j < n; j++) {for (k = 1, l = 2; k < n; k++, l <<= 1) {if (i & l) continue;D[i + l][k] = Min(D[i + l][k], D[i][j] + a[j][k]);

}}

}

for (i = 1; i < n; i++) ans = Min(ans, D[m - 1][i] + a[i][0]);printf("%d\n", ans);return 0;

}

동적계획법 (Dynamic Programming)

- 65 -

문제 교통수단 선택하기 (3008)

#include <stdio.h>#define swap(x, y) {data z = x; x = y; y = z;}

int N, M, T, L, r;int dis[110][110], rent[110], time[4], cost[4];int chk[110][1010][2];struct data {int n, t, byc, c;bool operator<(const data &r) const {if (c != r.c) return c < r.c;return t < r.t;

}} heap[200200];

void push(int p){if (p <= 1 || heap[p/2] < heap[p]) return;swap(heap[p/2], heap[p]);push(p / 2);

}

void pop(int p){int np = p * 2;if (np < r && heap[np + 1] < heap[np]) np++;if (np > r || heap[p] < heap[np]) return;swap(heap[p], heap[np]);pop(np);

}

algorithm

- 66 -

void inq(int n, int t, int c, int byc){if (t > T || chk[n][t][byc] <= c) return;chk[n][t][byc] = c;r++;heap[r].n = n, heap[r].t = t, heap[r].c = c, heap[r].byc = byc;push(r);

}

int bfs(){data top;int dist, i;inq(1, 0, 0, 0);while(r) {top = heap[1];if (top.n == N && top.byc == 0) return top.c;heap[1] = heap[r--];pop(1);if (rent[top.n]) inq(top.n, top.t, top.c, 1 - top.byc);for(i = 1; i <= N; i++) {dist = dis[top.n][i];if (dist == 0) continue;if(top.byc) inq(i, top.t + dist * time[2], top.c + dist * cost[2], 1);else {inq(i, top.t + dist * time[1], top.c, 0);inq(i, top.t + dist * time[3], top.c + dist * cost[3], 0);

}}

}return 0;

}

동적계획법 (Dynamic Programming)

- 67 -

int main(){int i, j, s, e, d;scanf("%d %d %d %d", &N, &M, &T, &L);for (i = 1; i <= M; i++) {scanf("%d %d %d", &s, &e, &d);dis[s][e] = dis[e][s] = d;

}for (i = 1; i <= L; i++) {scanf("%d", &s);rent[s] = 1;

}for (i = 1; i <= 3; i++) scanf("%d", &time[i]);for (i = 2; i <= 3; i++) scanf("%d", &cost[i]);for (i = 0; i <= N; i++)for (j = 0; j <= T; j++)chk[i][j][0] = chk[i][j][1] = 1<<20;

printf("%d\n", bfs());return 0;

}

알고리즘 문제해결다이나믹 프로그래밍 (Dynamic Programming)발행일저 자

년 월 일2017 2 1김 동 규

펴낸곳 한컴에듀케이션 주( )주소 경기도 안양시 동안구 평촌대로 협성골드프라자| 109 601전화번호|031-388-0999팩스|031-388-0996E-Mail|[email protected]