백준의 퇴사(14501) 문제이다.
[ 문제 바로가기 ]
(본 문제는 백준의 퇴사2(14586) 문제에 대한 풀이가 아닌 , 퇴사(14501) 문제에 대한 두 번째 풀이입니다.
관련된 풀이를 원하시는 분들은 아래 링크를 이용해 주세요.
퇴사2(14586) 풀이 보러가기 → [ 퇴사2(14586) 풀이 바로가기 ]
퇴사(14501) 첫 번째 풀이 보러가기 → [ 퇴사(14501) 풀이 바로가기 ] )
[ 문제풀이 ]
퇴사에 대한 풀이 글이 위에 걸어놓은 링크처럼 있는데, 위의 글에서는 이 문제를 재귀를 이용한 완전탐색 방식으로 구현하였다.
이 글에서는 이 문제를 Dynamic Programming 기법으로 풀이하는 방법을 이야기 해볼 것이다.
먼저 본인은 이 문제에 접근할 때, 역순으로 접근해 주었다. 다시 말해서 , 1일부터 N일 까지 일할 때 벌 수 있는 최대이익을 구한 것이 아니라, 역으로 N일부터 1일까지 일을 한다고 가정했을 때, 벌 수 있는 최대이익을 구해주었다.
왜 ?? 1일부터 할 경우 계산이 너무 복잡해지기 때문이다.
예를 들어서 1일날 일을 하고 1일날 일함으로써 소모되는 날이 x라고 가정했을 때 , 1 + x 날 일했을 때 얻을 수 있는 수익과, 1+ x날 일을 하지 않았을 때 얻을 수 있는 수익 등등... 비교해야 하는 것들과 연산해야 되는 것들이 많아지는 것 같아서 역순으로 계산을 해 주었다.
헷갈리지 말자. 일은 반드시 1일부터 N일까지 해야 하는데 , 우리는 계산만 N일에서 1일로 역순으로 하는 것이다.
하나의 예를 들어보면 N번째 날 일을 함으로써 소모되는 날이 2일인데 , 역순으로 계산한다고 해서, N번째 날 일을하면 N - 1번째 날은 일을 못하고, N - 2번째 날부터 다시 일을 할 수 있다 ! 라고 계산을 하면 안된다는 것이다.
우리는 최대이익만 역순으로 계산할 뿐이지, 시간이 흐르는 것은 1 ~ N일 순으로 흐른다는 것을 헷갈리지 말자.
그럼 문제에 나와있는 예제입력1 번을 통해서 문제를 해결해보자.
[ (3 , 10) , (5 , 20) , (1 , 10) , (1 , 20) , (2 , 15) , (4 , 40) , (2 , 200) ]
이렇게 총 7일동안 일할 때 소모되는 날과 이익이 적혀있다.
위에서도 말했듯이 가장 마지막 날부터 이익을 구해보자.
첫 번째로 7번째 날이다. 일을 할 수 있을까 ?? 할 수 없다. 왜냐하면 , N + 1번째날 바로 퇴사를 해버려야 하는데 , 7번째 날 일을 하게 되면 2일을 소모하게 되는 것이고, 이는 N + 1번째 날 퇴사를 할 수 없게 되기 때문에 일을 할 수가 없다.
따라서 7번째 날 얻을 수 있는 최대이익은 0원이다.
두 번째로 6번째 날을 보자. 이 날 또한 7번째 날과 동일한 이유로 일을 할 수가 없다.
따라서 6번째 날 얻을 수 있는 최대이익은 0원이다.
세 번째로 5번째 날을 보자. 이 날은 처음으로 일을 할 수 있는 날이다. 왜냐하면 일을 하더라도 N + 1번째 날 퇴사가 가능하기 때문이다.
일을 할 수 있다고 해서 반드시 일을 해야하는 것은 아니다. 왜냐하면 일을 하지 않고 그냥 넘어감으로써 더 큰 이익을 얻는 경우도 발생할 수 있기 때문에 일을 반드시 해야 하는 것은 아니다.
그럼 5번째 날 크게 보면 2가지 경우가 존재한다.
1. 일을 하는 Case.
2. 일을 하지 않는 Case.
만약 일을 하게 된다면 얼마의 이익을 얻을 수 있을까 ??
바로, 5일날 일을 함으로써 벌 수 있는 이익 + 7일날 일을 함으로써 벌 수 있는 이익 이 될 것이다.
왜 갑자기 7일이 나왔을까 ?? 5일날 일을 하면 2일을 소모하게 되고 , 가장 빠르게 그 다음 일을 시작할 수 있는 날이 7일이 된다. 따라서 7일날 일을 함으로써 벌 수 있는 이익이 되는 것이다.
이 경우, 벌 수 있는 이익이 5일날 일 함으로써 버는 이익 '15' + 7일날 일 함으로써 버는 이익 '0' = 15 의 이익을 얻게 된다.
두 번째로 일을 하지 않는다면 ???
일을 하지 않는다면 , 6일날 일을 함으로써 벌 수 있는 최대이익이 될 것이다. 왜 ?? 5일날 일을 하지 않으면, 그 다음날인 6일날 바로 일을 할 수 있게 되고, 이 때 얻을 수 있는 최대이익이 5일날 일을 함으로써 얻게되는 최대 이익이 될 것이다.
이 경우에는 6일날 벌 수 있는 최대이익 = '0' 의 이익을 얻게된다.
( 바로 이런 부분 때문에 역순으로 계산을 한 것입니다. 만약 위의 내용을 계산할 때, 7일날과 6일날 일을 함으로써 벌 수 있는
이익을 몰랐다면 계산이 위의 내용처럼 간단하지만은 않았을 것입니다. )
따라서 5일날 벌 수 있는 최대이익은 15와 0중에 더 큰 값인 15가 된다.
4일날을 보자. 이 경우에도 일을 할 수 있다. 일을 하더라도, N + 1번째 날 퇴사가 가능하기 때문이다.
이 경우에도 2가지 선택권이 생긴다. 일을 하느냐 마느냐 !
일을 하게 될 경우 얻게 되는 최대이익은 "4일날 일을 함으로써 얻게 되는 이익 + 5일날 얻을 수 있는 최대이익" 이 된다.
왜 ?? 4일날 일을 하면 하루를 소모하게 되고, 바로 5일날 부터 새로운 일을 다시 할 수 있기 때문이다.
그럼 이 때 얻을 수 있는 이익은 '4일날 일을 함으로써 얻게되는 이익(20) + 5일날 얻을 수 있는 최대이익(15)' = 35 가 된다.
일을 하지 않을 경우에는 "5일날 얻을 수 있는 최대이익"이 된다. 왜냐하면 일을 하지 않고 넘어가게 되면, 바로 5일날 부터 새 일을 다시 시작할 수 있기 때문에, 이 경우에는 얻을 수 있는 최대이익이 '5일날 일을 함으로써 얻을 수 있는 최대이익' 이 된다.
따라서 '15'가 되는데, 2개의 값을 비교해보면 4일날 얻을 수 있는 최대이익은 '35'가 된다.
그럼 여기서 위의 내용들을 토대로 일반화를 한번 시켜보자.
먼저 본인이 이익을 계산하기 전에 항상 체크해 주었던게 있다. 바로 위에서 분홍색 진한 글씨로 표현된 조건들인데
바로, "해당 날 일을 하게 되면, N + 1번째 날 퇴사가 가능한지?" 이다. 퇴사가 불가능하다면 선택권이 없이 무조건 일을 못하게 된다.
만약 N + 1번째 날 퇴사가 가능하다면 2가지 선택권이 생기게 된다. 일을 하느냐 마느냐 !
지금부터는 하나의 수식으로 표현해보자. F[A] = B !
F[A] = B의 의미는 "A번째 날 일을 함으로써 얻게되는 최대이익은 B입니다." 이다.
그리고 A번째 날 일을 함으로써 , 'Day'만큼의 날짜가 소모되고 , 'Cost'만큼의 이익을 얻을 수 있다고 가정해보자.
그럼 A번째 날 일을 하게 되면 얻을 수 있는 이익은
F[A] = F[A + Day] + Cost 가 된다.
일을 하지 않게 되면 얻을 수 있는 이익은 F[A] = F[A + 1] 이 된다.
2개의 값 중에서 더 큰 값이 F[A]의 값이 되는 것이다.
이 부분을 본인이 구현한 코드는 아래와 같다.
1 2 3 4 5 6 7 8 9 | void Solution() { for (int i = N; i >= 1; i--) { if (i + Day[i] > N + 1) DP[i] = DP[i + 1]; else DP[i] = Max(DP[i + 1], DP[i + Day[i]] + Cost[i]); } cout << DP[1] << endl; } | cs |
굳이 코드를 가져온 이유는 설명할 부분이 하나 있어서이다.
먼저 line3)을 보면 처음에 말했듯이, N번째 날부터 1일까지 역순으로 구하기 위해서이다.
line5~6)에 걸려있는 if-else 문이, "N + 1번째 날 퇴사가 가능하냐 아니냐" 를 묻는 조건문이다.
line5) 같은 경우에는 N + 1번째 날 퇴사를 하지 못하는 경우를 line6) 같은 경우에는 N + 1번째 날 퇴사를 할 수 있는 경우를 나타낸 것이다. 그런데 ! line5)에 적힌 조건문에 이상한 점이 하나 있다.
바로, " i + Day[i] > N + 1 " 이라고 적혀있다는 것이다. 왜 " i + Day[i] >= N + 1 "이 아닐까 ??
분명, N + 1번째날 퇴사를 한다고 했는데, 마치 N + 2번째 날 퇴사를 하는 것 처럼 표현해놓은 것일까 ??
이 부분은 이 문제를 코드로 표현하다 보니 저렇게 표현한 것이다.
예를 들어서 N = 7 이고 , 8일날 퇴사를 한다고 가정해보자. 그리고 7일날 일을 함으로써 소모되는 날은 '1일'이라고
가정해보자. 그럼 우리는 7일날 일을 할 수 있을까 없을까?? 일을 할 수 있다. 어차피 하루만 소모하기 때문에 7일날 일을 하고 8일날 퇴사를 하면 된다.
그런데 ! 위의 조건문을 "i + Day[i] >= N + 1" 이라고 설정해놓고 계산을 한다면
"7 + 1 >= 8" 로써 if문에 조건을 만족해서 else로 가지 않고 if문의 내용을 수행하게 된다.
if문의 조건은 "N + 1번째 날 퇴사를 하지 못하는 경우" 에 해당하는 조건문이다.
그런데 ! >= 으로 해버리는 순간, 7일날 일을 할 수 있음에도 불구하고, 8일날 퇴사를 하지 못하는 조건문이 실행되는 상황이 발생하는 것이다.
왜 그러냐면 우리는 머릿속으로는 7일에서 하루 일하면, 8일부터 일이 가능하다 ! 라는 것을 알고 있지만, 코드로 구현할 때
7 + 1 >= 8 이냐고 묻게 되면 그렇다 라고 프로그램은 대답하기 때문이다. 따라서 , 이런 경우를 막아주기 위해서 위와 같이 조건문을 걸어준 것이다.
[ 소스코드 ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | #include <iostream> #define endl "\n" #define MAX 25 using namespace std; int N, Answer; int Day[MAX]; int Cost[MAX]; int DP[MAX]; int Max(int A, int B) { if (A > B) return A; return B; } void Input() { cin >> N; for (int i = 1; i <= N; i++) { cin >> Day[i] >> Cost[i]; } } void Solution() { for (int i = N; i >= 1; i--) { if (i + Day[i] > N + 1) DP[i] = DP[i + 1]; else DP[i] = Max(DP[i + 1], DP[i + Day[i]] + Cost[i]); } cout << DP[1] << endl; } void Solve() { Input(); Solution(); } int main(void) { ios::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL); //freopen("Input.txt", "r", stdin); Solve(); return 0; } | cs |
'[ BOJ Code ] > # BOJ -' 카테고리의 다른 글
[ 백준 9465 ] 스티커 (C++) (2) | 2020.08.07 |
---|---|
[ 백준 11052 ] 카드 구매하기 (C++) (10) | 2020.08.06 |
[ 백준 9461 ] 파도반 수열 (C++) (2) | 2020.08.05 |
[ 백준 11053 ] 가장 긴 증가하는 부분 수열 (C++) (4) | 2020.08.04 |
[ 백준 10844 ] 쉬운 계단 수 (C++) (0) | 2020.08.03 |