[ 백준 2156 ] 포도주 시식 (C++)
백준의 포도주 시식(2156) 문제이다.
[ 문제 바로가기 ]
[ 문제풀이 ]
간단한 예시를 통해서, 포도주를 어떻게 마셨을 때, 가장 최대로 마실 수 있는지 알아보자.
5개의 포도주 잔이 있고, 그 5개의 잔에 들어있는 포도주의 양이 [ 1 , 2 , 3 , 4 , 5 ] 라고 가정해보자.
그럼 이 때, 마실 수 있는 최대양을 구해보자.
우리가 생각해줘야 할 조건은 딱 한가지가 있다. 바로 "연속한 3잔을 모두 마실 수는 없다" 이다.
그럼 지금부터는 포도주를 앞에서 부터 순서대로 마셔볼 건데, 무조건 마시는 상황이 아니라, "x번째 잔 까지 왔을 때, 마실 수 있는 포도주의 최대양" 을 기준으로 생각을 해보자. 즉, 내가 현재, x번째 잔 까지 왔는데, 이 x번째 잔을 무조건 마셨다고 가정하고 최댓값을 구하는 것이 아니라 , x번째 잔 까지 왔을 때의 최댓값을 구해보자. 왜냐하면, x번째 잔 까지 왔을 때, 해당 포도주 잔을 무조건 마시는 경우가 최대가 아니라 마시지 않았을 때 최댓값이 되는 경우도 존재하기 때문이다.
그럼 첫 번째 포도주 잔부터 알아보자. - "첫 번째 잔 까지 왔을 때, 마실 수 있는 포도주의 최대양"
당연히 '1'이다. 왜 ?? 첫 번째 포도주 잔 이전에는 그 어떤 포도주 잔이 없기 때문에, 이전까지 마신 포도주의 최대양은 0이라고 생각할 수 있다. 그렇기 때문에 첫 번째 잔 까지 왔을 때, 마실 수 있는 포도주의 최대양은 무조건 첫 번째 포도주 잔을 마셨을 때 얻을 수 있는 양인 '1'이 된다. 위에서 언급한 조건 따위는 생각하지 않아도 되는 경우이다.
[ 첫 번째 포도주 까지 마실 수 있는 포도주의 최대양 = 1 ]
두 번째 포도주 잔을 알아보자. - "두 번째 잔 까지 왔을 때, 마실 수 있는 포도주의 최대양"
먼저 두 번째 잔 까지 오는 경우를 한번 생각해보자.
1. 첫 번째 잔을 마시고 → 두 번째 잔으로 오는 경우
2. 첫 번째 잔을 마시지 않고 → 두 번째 잔으로 오는 경우
이렇게 2가지 경우가 존재한다. 보다 정확하게 말하자면, 총 4가지 경우가 발생한다. 왜 ? 위의 2가지 경우에는 단순히 "두 번째 잔으로 오는 경우" 라고만 표시를 해두었지, 두 번째 잔을 마시냐 안마시냐에 대해서는 언급하지 않았다. 즉 ! 위의 2가지 경우에서 각 경우마다, 두 번째 잔을 마시는 경우와 마시지 않는 경우가 존재하기 때문에 보다 정확하게 말하자면 총 4가지 경우가 발생하는 것이다. 그런데 ! 과연 두 번째 잔을 계산해야 하는 상황에서 "두 번째 잔을 마시지 않는 경우" 를 생각해주어야 할까 ??? 생각해줄 필요가 없다. 왜 ? 문제에서 제시된 조건인 "연속된 3잔은 모두 마실 수 없다" 에 아무런 영향을 미치지 않기 때문이다. 이 두 번째 잔을 마심으로 인해서, "연속된 3잔을 모두 마시게 되므로 조건에 위배된다" 라는 상황이 발생한다면, 마시는 경우와 마시지 않는 경우를 구분해서 계산을 하겠는데, 기껏 해봤자 이제 두 번째 잔이다. 첫 번째 잔을 마시든 안마시든, 두 번째 잔을 마시든 안마시든 상관없이 절대로 "연속된 3잔은 모두 마실 수 없다" 라는 조건에 위배되지가 않는다. 그래서 위와 같이 적어놓았다.
위의 2가지 경우 모두, 조건에 위배되지 않는다. 즉 ! 위의 2가지 경우 중에서 더 큰 값이 바로 두 번째 잔 까지 왔을 때, 마실 수 있는 포도주의 최대양이 될 것이다.
계산을 해보면 첫 번째 경우는 1 + 2 = 3 이 되고 , 두 번째 경우는 2 가 된다. 따라서, 첫 번째 경우처럼 마셨을 경우, '3'이라는 최대값을 구할 수 있다. 그런데 ! 사실 위에서 말한 2가지 경우 중에, 두 번째 경우는 고려를 해볼 필요도 없다.
왜냐하면 포도주의 양은 1000이하의 음이 아닌 정수이기 때문에, 최소 0이라는 것을 의미한다.
즉 ! 첫 번째 잔을 마시고 두 번째 잔을 마시는 경우가 무.조.건 첫 번째 잔을 마시지 않고 두 번째 잔으로 바로오는 경우보다 크거나 같아진다.
[ 두 번째 포도주 까지 마실 수 있는 포도주의 최대양 = 3 ]
세 번째 포도주 잔을 알아보자. - "세 번째 잔 까지 왔을 때, 마실 수 있는 포도주의 최대양"
세 번째 포도주 잔은 위에서 이야기한 첫 번째 , 두 번째 포도주잔들과는 조금 다른 이야기를 시작해야 한다. 왜 ?? 처음으로 조건을 생각해줘야 하는 포도주 잔이기 때문이다.
먼저, 세 번째 잔 까지 왔을 때 마실 수 있는 경우를 생각해보자. 말로 하나하나 적으면 너무 기니까 간단하게 O , X로만 표시를 해보자. 예를 들어서 O O O 는 첫 번째 잔을 마시고, 두 번째 잔을 마시고, 세 번째 잔을 마시는 경우를 의미한다.
1. O O X
2. O X O
3. X O O
4. X O X
5. X X O
6. O X X
7. X X X
제시된 조건에 위배되지 않고 세 번째 포도주 잔까지 왔을 때 발생할 수 있는 경우의 수는 위와 같이 5가지가 존재한다.
첫 번째 경우 얻을 수 있는 포도주의 양 = 1 + 2 = 3
두 번째 경우 얻을 수 있는 포도주의 양 = 1 + 3 = 4
세 번째 경우 얻을 수 있는 포도주의 양 = 2 + 3 = 5
네 번째 경우 얻을 수 있는 포도주의 양 = 2
다섯 번째 경우 얻을 수 있는 포도주의 양 = 3
여섯 번째 경우 얻을 수 있는 포도주의 양 = 1
일곱 번째 경우 얻을 수 있는 포도주의 양 = 0
위의 값들을 비교해면 세 번째 경우인, X O O 로 마셨을 경우 최대 '5'를 마실 수 있다. 그런데 ! 이렇게 다 비교를 할 수는 없다. 네 번째와 다섯 번째로 가면 갈수록 점점 더 경우의 수가 많아질텐데 이렇게 하나하나 비교를 할 수는 없다.
그래서 위의 경우들에서 딱히 비교해보지 않아도 되는, 공통된 특징을 가지는 놈들끼리 한번 묶어보자.
{ 1 , 4 , 6 , 7 } 번 Case.
본인은 가장 먼저 위의 4가지 Case를 하나로 묶어 보았다. 공통점을 찾아보면, 모두 "세 번째 잔을 마시지 않는 경우" 라는 것이다. 이 Case들은 3번째 잔을 마시지 않기 때문에 어쩌면 문제의 조건에 위배될 상황이 절대로 발생하지 않을 놈들이다.
그리고 1 , 4 , 6 , 7 번 Case들 중에서 최댓값을 찾아보면, 1번 Case인 O O X가 '3'으로 최댓값이 되는데, 이 값은 "두 번째 잔 까지 왔을 때 마실 수 있는 포도주의 최대양"을 의미한다. 즉 ! "내가 현재 x번째 잔까지 왔을 때, x번째 잔을 마시지 않는다고 가정한다면, 이 때 얻을 수 있는 포도주의 최대양은 x - 1번째 포도주잔까지 왔을 때 마실 수 있는 포도주의 최대 양" 이 된다는 것이다.
위의 한 마디 문장으로 , { 1 , 4 , 6 , 7 } 4개의 Case를 하나로 추려내서 계산을 할 수가 있다.
즉 ! 내가 현재 N번째 잔일 때, N번째 잔을 마시지 않는다고 가정할 경우, 마실 수 있는 포도주의 최대양은 N - 1번째 잔까지 왔을 때 마실 수 있는 포도주의 최대양 과 같다 !
그럼 계산하지 않은 { 2 , 3 , 5 } 번 Case들을 보자. 이 Case들은 공통점이 "세 번째 잔을 마시는 경우" 라는 것이다.
이 Case들의 공통점을 한 가지 더 찾아보자면 "세 번째 잔을 마시기 위해서 , 최소 ! 첫 번째 혹은 두 번째 잔 중 하나를 마시면 안된다는 것" 이다.
2번 Case는 O X O 로, 두 번째 포도주 잔을 마시지 않은 경우이다.
3번 Case는 X O O 로, 첫 번째 포도주 잔을 마시지 않은 경우이다.
5번 Case는 X X O 로, 첫 번째, 두 번째 포도주 잔을 모두 마시지 않은 경우이다.
그럼 여기서 우리가 계산하고자 하는 포도주 잔이 세 번째가 아니라 굉장히 큰 숫자라고 한번 가정을 해보자.
그 전에 포도주 잔 중에 어떤 잔을 마셨는지 마시지 않았는지는 모르겠다. 그런데 다음과 같은 상황이라고 가정을 해보자.
? ? ? ? ? ? ? ? ? ? ? ? ? O X O
? ? ? ? ? ? ? ? ? ? ? ? ? X O O
? ? ? ? ? ? ? ? ? ? ? ? ? X X O
위와 같은 상황인 것이다. 위와 같은 상황이더라도, '?'의 상태들이 마셨는지 안마셨는지 구체적으로는 모르지만, 절대로 조건에 위배되는 상황은 발생하지 않는다 라고만 한다면, 위의 상황처럼 포도주를 마실 수 있게 된다.
이 상황에서 가장 마지막에 'O'로 체크된 포도주 잔을 N 번째 잔이라고 가정하고, 위의 3가지 Case들을 계산해보자.
1번 Case = N - 2번째 잔까지 왔을 때, 마실 수 있는 포도주 양의 최댓값 + N번째 포도주의 양
2번 Case = N - 3번째 잔까지 왔을 때, 마실 수 있는 포도주의 양의 최댓값 + N - 1번째 포도주의 양 + N번째 포도주의 양
3번 Case = N - 3번째 잔까지 왔을 때, 마실 수 있는 포도주의 양의 최댓값 + N번째 포도주의 양
이렇게 정리를 할 수가 있다. 그런데 3번 Case를 주목해보자. 최댓값이 될 가능성이 있는 놈일까 ?? 절대로 없는 놈이다. 왜 ?? 2번 Case에서 N - 1번째 포도주의 양 만큼을 안 마시고 버린 경우와 똑같기 때문이다. 포도주의 양의 최솟값은 0이기 때문에 마실 수 있다면 무조건 마시는게 최댓값에 영향을 미칠 것이다. 그런데, 2번 Case 처럼 마실 수도 있는 포도주 잔인데도, 굳이 마시지 않고 버려놓고, 2번 Case보다 큰 값이 발생할 수 있을까 ?? 절대로 그럴 수가 없다. 즉 ! 3번 Case는 비교할 필요가 없다.
그리고 여기서 정리를 해보자. 내가 현재 N번째 잔 까지 왔을 때, N번째 잔을 마신다고 가정했을 때, 마실 수 있는 포도주의 최대양은 'N - 2번째 잔까지 마셨을 때 얻을 수 있는 최대 양 + N번째 잔을 마셨을 때 얻을 수 있는 포도주의 양 ' & 'N - 3번째 잔 까지 마셨을 때 얻을 수 있는 최대 양 + N - 1번째 포도주의 양 + N번째 포도주의 양' 중 더 큰 값이 된다.
그럼 ! a번째 잔 까지 왔을 때, 얻을 수 있는 포도주의 최대양은 b 이다 라는 것을 DP[a] = b 라는 식으로 표현해보자.
DP[1] = Arr[1] 이 될 것이고 , DP[2] = Arr[1] + Arr[2] 가 될 것이다. 이 경우, DP[N]의 값을 구해보면..
DP[N] = Max(DP[N - 3] + Arr[N - 1] + Arr[N] , DP[N - 2] + Arr[N] , DP[N - 1]) 이 된다.
마시지 않는 경우 발생할 수 있는 1가지와, 마시는 경우 발생할 수 있는 2가지 경우, 총 3가지 경우 중 최댓값이 N번째 잔까지 왔을 때, 마실 수 있는 포도주의 최대양이 된다.
이를 다시 돌아와서 4번째 , 5번째 포도주 잔에 적용시켜보자.
DP[4] = ? 위의 식에 그대로 적용시켜보면
DP[4] = Max(DP[1] + Arr[3] + Arr[4] , DP[2] + Arr[4] , DP[3]) 이 된다.
DP[1] + Arr[3] + Arr[4] = 1 + 3 + 4 = 8
DP[2] + Arr[4] = 3 + 4 = 7
DP[3] = 5
즉 ! 최댓값은 8이 된다.
DP[5] = Max(DP[2] + Arr[4] + Arr[5] , DP[3] + Arr[5] , DP[4]) 가 된다.
이런식으로 계산을 해주면 정답을 도출할 수 있다.
[ 소스코드 ]
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 | #include <iostream> #define endl "\n" #define MAX 10010 using namespace std; int N; int Arr[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 >> Arr[i]; } void Solution() { DP[1] = Arr[1]; DP[2] = DP[1] + Arr[2]; for (int i = 3; i <= N; i++) { DP[i] = Max(DP[i - 3] + Arr[i - 1] + Arr[i], DP[i - 2] + Arr[i]); DP[i] = Max(DP[i - 1], DP[i]); } cout << DP[N] << 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 |