효주는 포도주 시식회에 갔다. 그 곳에 갔더니, 테이블 위에 다양한 포도주가 들어있는 포도주 잔이 일렬로 놓여 있었다. 효주는 포도주 시식을 하려고 하는데, 여기에는 다음과 같은 두 가지 규칙이 있다.
효주는 될 수 있는 대로 많은 양의 포도주를 맛보기 위해서 어떤 포도주 잔을 선택해야 할지 고민하고 있다. 1부터 n까지의 번호가 붙어 있는 n개의 포도주 잔이 순서대로 테이블 위에 놓여 있고, 각 포도주 잔에 들어있는 포도주의 양이 주어졌을 때, 효주를 도와 가장 많은 양의 포도주를 마실 수 있도록 하는 프로그램을 작성하시오.
예를 들어 6개의 포도주 잔이 있고, 각각의 잔에 순서대로 6, 10, 13, 9, 8, 1 만큼의 포도주가 들어 있을 때, 첫 번째, 두 번째, 네 번째, 다섯 번째 포도주 잔을 선택하면 총 포도주 양이 33으로 최대로 마실 수 있다.
첫째 줄에 포도주 잔의 개수 n이 주어진다. (1≤n≤10,000) 둘째 줄부터 n+1번째 줄까지 포도주 잔에 들어있는 포도주의 양이 순서대로 주어진다. 포도주의 양은 1,000 이하의 음이 아닌 정수이다.
첫째 줄에 최대로 마실 수 있는 포도주의 양을 출력한다.
과정을 생략하고 코드를 보고싶은 분은 여기로
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
int arr[t];
for (int i = 0; i < t; i++) {
cin >> arr[i];
}
for (int i = 0; i < t; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
계단문제(BOJ:2579번)와 비슷하다고 생각했다. 연속해서 3개의 포도잔을 마실 수 없다 == 연속해서 계단 3개를 오를 수 없다.
동적 프로그래밍을 사용하는 문제에서는 보통 연속해서 3개의 어떤 행위를 할 수 없는 것 같다.
최대의 포도주를 시식해야되니까(😏 주당이네) i 번째의 행위를 할 때, 최고의 i-1 또는 i-2번째 행위를 고려하면 된다. DP의 식을 세워야 한다.
3번째 포도주를 마실때를 생각해보자. 1 번째, 2 번째, 3 번째 순서대로 전부 마시고 싶지만,,,, 취하니까 못먹게 하나보다. 그렇다면 생각해야한다. 1번째, 3번째 포도주를 마실것이냐! 아니면 2번째, 3번째 포도주를 마실것이냐! 이 생각부터 시작했다. 1 → 3으로 연속적이지 않게 포도주를 마실것인지, 2 → 3으로 연속으로 포도주를 마실 것인지 생각을 해야한다! 그렇다면 좀 더 자세히 생각해보자.
즉, DP배열은 2차원 배열로 필요할 것 같다.
dp[i][0] → i 번째 포도주를 첫 번째로 마시는 경우
dp[i][1] → i 번째 포도주를 두 번째로 마시는 경우
dp[i][0] → i 번째 포도주를 첫번째로 마시는 경우! i-1 번째 포도주는 마시면 안된다! 그러므로 i-2번째의 포도주를 마시는데, i-2 번째의 포도주를 어떻게 먹든 최대값으로 먹는 경우를 골라야 하니
max( dp[i-2][0](첫번째로 마신 경우), dp[i-2][1](두번째로 마신 경우) )
둘중에서 큰 값을 골라보자.
int dp[t][2];
dp[0][0] = arr[0];
dp[0][1] = 0;
dp[1][0] = arr[1];
dp[1][1] = dp[0][0] + arr[1];
for(int i=3;i<t;i++){
//dp[i][0] => i번째 요소를 처음 선택하는 경우
dp[i][0] = max(dp[i-2][0],dp[i-2][1]) + arr[i];
//dp[i][1] => i번째 요소를 2번째로 선택하는 경우
dp[i][1] = dp[i][0] + arr[i];
}
// t번째에서 큰걸 고른다.
cout<<max(dp[t-1][0],dp[t-1][1]);
오잉...틀렸다.
뭐가 잘못된 걸까?? 😰
일단 오타가 있었다.
dp[i][1] = dp[i][0] + arr[i]; → dp[i][1] = dp[i-1][0] + arr[i];
그랬더니 값이 더 이상하게 나온다. 😭
두번째 오타를 발견했다.
for문의 i 가 3부터 시작한다. → for 문의 i를 2로 바꿔줘야한다.
어쩐지 쓰레기값이 계속 나오더라.
근데 t번째가 29, 32이다! 왜지?
생각을 해봤더니...
굳이 t번째가 마지막이 될 필요가 없지 않나? t-1번째가 마지막이 되어도 되잖아?
예제도 그렇다. t번째가 1이니까
t-1번째를 못먹고 t를 먹기보다는
t를 포기하고 t-1번째를 먹는다!
이렇게 될 수 있다고 생각했으니 dp[t] 와 dp[t-1]중에서 max를 고르면 되겠다.
코딩해보자.
int a = max(dp[t-1][0],dp[t-1][1]);
int b = max(dp[t-2][0],dp[t-2][1]);
cout<<max(a,b)<<endl;
이거지 이거지 👺
정답이다!
이제 제출하기 전에 체크하자 (특히 범위....주유소 문제에서 호되게 당했다.)
그렇다. 마지막에 dp[i-1]을 탐색하기 때문에 쓰레기값이 나온다. 수정하자.
int a = max(dp[t-1][0],dp[t-1][1]);
int b = 0;
if(t == 1){
b = 0;
}else{
b = max(dp[t-2][0],dp[t-2][1]);
}
cout<<max(a,b)<<endl;
최대값은 테스트가 힘드니까...(10,000 번 입력할꺼야? 👊 💢 )
반례가 있는가?
일단 생각나는건 없다!
2%에서 틀렸다. 기억하자.
누군가 말했다. 안먹을 수 도 있다고..... 😢 아니 왜 안먹고 그르세요...
200 400 1 2 400 500 인 경우를 생각해보면 1, 2를 먹는건 손해 of 손해다. 나도 그렇게 생각한다. 분명히 안먹고 지나가는 경우가 있다.
그렇다면 또 생각해야 하는건 3번 이상 안먹고 지나가는 경우가 있나? → 200 400 1 1 1 400 500인 경우에는 중간에 1을 고르고 가면 되니까....3번 이상 안먹고 지나가는 경우는 없다! 라고 생각했다.
그럼 바로 코딩해보자.
2번이상을 연속으로 안먹는 경우? → 구글링을 통해서 해법을 찾았다.
dp[i] = max(dp[i],dp[i-1])이라고 한다
왜 그런지 솔직히 아직까지 이해가 안된다. 400을 고르는 순간에 2를 고른것과 비교를 한다?
나는 아직 많이 부족한 것 같다. 더 열심히 공부하자.
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
int arr[t];
for (int i = 0; i < t; i++) {
cin >> arr[i];
}
// for (int i = 0; i < t; i++) {
// cout << arr[i] << " ";
// }
// cout << endl;
int dp[t][2];
dp[0][0] = arr[0];
dp[0][1] = 0;
dp[1][0] = arr[1];
dp[1][1] = dp[0][0] + arr[1];
for(int i=2;i<t;i++){
//dp[i][0] => i번째 요소를 처음 선택하는 경우
dp[i][0] = max(dp[i-2][0],dp[i-2][1]) + arr[i];
//dp[i][1] => i번째 요소를 2번째로 선택하는 경우
dp[i][1] = dp[i-1][0] + arr[i];
//세개 이상을 안 고르고 건너뛸 수 있나? -> 세개 이상은 건너뛸 필요가 없다. 중간을 고르면 되그등요
dp[i][0] = max(dp[i][0],dp[i-1][0]);
dp[i][1] = max(dp[i-1][1],dp[i][1]);
}
int a = max(dp[t-1][0],dp[t-1][1]);
int b = 0;
if(t == 1){
b = 0;
}else{
b = max(dp[t-2][0],dp[t-2][1]);
}
cout<<max(a,b)<<endl;
return 0;
}