백준의 계단오르기 (2579) 문제이다.
[ 문제 바로가기 ]
[ 문제풀이 ]
주어진 규칙에 따라서 계단을 오를 때, 획득할 수 있는 점수의 최댓값을 구해야 하는 문제이다.
예시를 하나 가지고 알아보자.
7칸짜리 계단이 있고, 계단은 시작점부터 [ 1 , 2 , 3 , 4 , 5 , 6 , 7 ] 의 점수를 순서대로 가지고 있다고 가정해보자.
그리고 이 계단을 가장 아래에 있는 계단부터 올라가보자. 단 ! 문제에서는 어떤 계단을 밟고 어떤 계단을 밟지 않을수도 있지만(주어진 규칙 상 모든 계단을 다 밟을 수는 없다) 지금부터는 한 계단 한 계단씩 올라가면서 점수를 계산해볼 것인데,
그 계단을 "반드시 밟았을 때" 얻을 수 있는 최댓값을 구해보자. 지금부터 "x번 계단까지 올라갔을 때 얻을 수 있는 점수의 최댓값은?" 이라는 말은 곧, "x번 계단까지 올라왔고, 그 x번 계단을 반드시 밟았을 때, 얻을 수 있는 점수의 최댓값은 ?" 으로 해석하면 된다.
먼저, 첫 번째 계단까지 올라갔을 때 얻을 수 있는 점수의 최댓값은 얼마일까 ??
당연히 '1'일 것이다. 그럼 이 당연한 부분에서 왜 그렇게 되는지 이유를 한번 찾아보자.
우리는 지금 '1'의 점수를 가진 첫 번째 계단에 대해서 이야기를 하고 있고, 위에서 말했듯이 "첫 번째 계단을 반드시 밟았을 때의 얻을 수 있는 점수의 최댓값" 에 대해서 이야기를 하고 있다.
즉 ! 첫 번째 계단 이전에 계단은 존재하지 않고, 이 존재하지 않는다는 것을 다시 말하면 점수가 0점이라고 표현할 수 있다.
즉 ! 첫 번째 계단까지 올라왔을 때 얻을 수 있는 최대 점수는 첫 번째 계단의 점수인 '1'점이다.
[ 첫 번째 계단까지 올라왔을 때 얻을 수 있는 최대 점수 = 1 ]
두 번째 계단으로 넘어가보자. 두 번째 계단까지 올라갔을 때, 얻을 수 있는 최대점수는 몇점일까 ??
바로 '3'점이다. 왜 ? 첫 번째 계단을 밟아서 얻을 수 있는 점수(1) + 두 번째 계단을 밟아서 얻을 수 있는 점수(2) 이렇게 되어서
이 때가 두 번째 계단까지 올라왔을 때, 그리고 이 두 번째 계단을 반드시 밟았다고 가정했을 때, 얻을 수 있는 최대점수는 3점이 된다.
[ 두 번째 계단까지 올라왔을 때 얻을 수 있는 최대 점수 = 3 ]
세 번째 계단으로 넘어가보자. 세 번째 계단부터는 약간 조금 복잡해진다. 왜냐하면 문제에서 제시된 규칙중에서 "연속된 3개의 계단을 모두 밟아서는 안된다." 라는 조건 때문에 첫 번째, 두 번째 계단들과는 달리, '첫 번째 계단 + 두 번째 계단 + 세 번째 계단' 으로 계단을 오를 수 없기 때문이다.
그럼 위에서 말한대로 지금 이 상황은 "세 번째 계단을 반드시 밟아야 하는 상황" 이다. 그럼 점수를 계산하기 전에, 어떻게 계단을 올라오면 "반드시 세 번째 계단을 밟을 수 있는지" 에 대해서 부터 생각을 해보자.
세 번째 계단을 반드시 밟을 수 있는 경우는 2가지 경우가 존재한다.
1. 시작점 - 1번계단 - 3번계단
2. 시작점 - 2번계단 - 3번계단
위의 2가지 방법으로 올라오게 되면, 조건에 위배되지 않고 세 번째 계단을 밟을 수 있게 된다.
그럼 위의 2가지 경우에서 더 큰 값을 가지는 경우가, 아마 "세 번째 계단을 반드시 밟았을 때, 얻을 수 있는 최댓값"이 될 것이다.
1번 경우는 1 + 3 = 4 점이 되고 , 2번 경우는 2 + 3 = 5 로, 2번 경우가 더 큰 값을 가지므로 세 번째 계단까지 올라왔을 때 얻을 수 있는 최대 점수는 5점이 된다.
[ 세 번째 계단까지 올라왔을 때 얻을 수 있는 최대 점수 = 5 ]
네 번째 계단으로 가보자. 세 번째 계단과 마찬가지로, 네 번째 계단을 밟을 수 있는 경우에 대해서 부터 알아보자.
1. 시작점 - 1번계단 - 2번계단 - 4번계단
2. 시작점 - 2번계단 - 4번계단
3. 시작점 - 1번계단 - 3번계단 - 4번계단
위와 같이 3가지 경우가 존재한다. 그럼 위의 3가지 경우들이 각각 몇 점을 얻을 수 있는지 알아보자.
1번 경우는 1 + 2 + 4 = 7 , 2번 경우는 2 + 4 = 6 , 3번 경우는 1 + 3 + 4 = 8 이 된다. 즉 ! 4번 계단까지 올라왔을 때, 얻을 수 있는 최대 점수는 8점이 된다.
그럼 다섯번째 계단으로 넘어가기 전에, 이제는 위의 상황들에서 하나의 일반화된 식을 한번 찾아보자.
그리고 5, 6, 7번 계단들에 대해서는 그 일반화된 식을 적용시켜서 그 식이 맞는지 검토용으로 한번 계산을 해보자.
마지막으로 계산했던, 네 번째 계산을 밟는 3가지 경우를 한번 봐보자.
1. 시작점 - 1번 계단 - 2번 계단 - 4번 계단
2. 시작점 - 2번 계단 - 4번 계단
3. 시작점 - 1번 계단 - 3번 계단 - 4번 계단
위와 같이 3가지 경우가 있었는데, 1번 Case와 2번 Case에 대해서 이야기를 해보자.
1번 Case, 2번 Case에는 하나의 공통점이 있다. 바로 "2번 계단을 밟는다는 것" 이다.
그럼, 2번 계단을 반드시 밟았을 때, 얻을 수 있는 최댓값은 얼마일까 ?? 무.조.건 "첫 번째 계단 + 두 번째 계단" 을 밟는 경우이다. 왜 ?? 계단에 쓰여진 점수는 10000 이하의 자연수 이기 때문에 계단의 점수에는 0 혹은 음수가 존재하지 않는다는 것이고, 양의 정수만 존재하는 상황에서, 시작점 -> 2번 계단 으로 한 번에 두 칸을 올라가는 경우가 절대로 시작점 -> 1번 계단 -> 2번 계단 을 통해서 올라가는 경우보다 더 큰 값을 가질 수가 없기 때문이다.
즉 !! 우리는 위에서 네 번째 계단을 밟는 경우의 수를 계산할 때, 위와 같이 3가지 경우를 비교해서 값을 구했지만, 사실상 2번 Case같은 경우에는 무조건적으로 1번 Case보다 값이 작을 수 밖에 없기 때문에 절대로 2번 Case가 최댓값이 될 수는 없다.
그럼 위의 경우들을 다시한번 적어보면, 이렇게 적을 수 있다.
1. 시작점 - 1번 계단 - 2번 계단 - 4번 계단
2. 시작점 - 1번 계단 - 3번 계단 - 4번 계단
그럼 위의 2가지 Case들을 일반화를 한번 시켜보자.
먼저 1번 Case를 살펴보자. 시작점 → 1번 계단 → 2번 계단 → 4번 계단 으로 가는 순서인데, 여기서 빨강색으로 색칠된 부분을 다르게 한번 바꿔보자. 시작점 → 1번 계단 → 2번 계단 은 우리가 위에서 계산했던 "2번 계단을 반드시 밟았을 때, 얻을 수 있는 점수의 최댓값" 과 같은 결과를 가지는 부분이다. 그럼 1번 Case에서 얻을 수 있는 점수의 최댓값은 이렇게 표현할 수 있다.
1. 2번 계단을 반드시 밟았을 때, 얻을 수 있는 점수의 최댓값 + 4번 계단을 밟았을 때 얻을 수 있는 점수
다음으로 2번 Case를 한번 살펴보자. 시작점 → 1번 계단 → 3번 계단 → 4번 계단 으로 가는 순서이다.
1번 Case와 같이 시작점 → 1번 계단 → 3번 계단 → 4번 계단 에서 빨강색으로 색칠된 부분을 "3번 계단을 반드시 밟았을 때, 얻을 수 있는 점수의 최댓값" 으로 바꿀 수 있을까 ?? 아니다 ! 바꿀 수 없다. 왜?? 위에 올려서 다시 읽어보면 알겠지만,
3번 계단을 오를 때 얻을 수 있는 점수의 최댓값은 시작점 → 2번 계단 → 3번 계단으로 가는 루트이기 때문에, 빨강색으로 색칠된 부분을 "3번 계단을 반드시 밟았을 때 얻을 수 있는 점수의 최댓값" 으로 바꿀 수는 없다.
그럼 ! 있는 그대로 다시한번 바꿔보자. 시작점 → 1번 계단 이라는 것은, 다르게 말하면, "1번 계단을 밟았을 때 얻을 수 있는 점수의 최댓값"을 의미한다. 그럼 2번 Case에서 얻을 수 있는 점수의 최댓값은 이렇게 표현할 수 있다.
2. 1번 계단을 반드시 밟았을 때, 얻을 수 있는 점수의 최댓값 + 3번 계단을 밟았을 때 얻을 수 있는 점수 + 4번 계단을 밟았을 때 얻을 수 있는 점수
이렇게 바꿀 수가 있다.
그럼 ! 위에서 파랑색 글씨들에서 4번 계단이라는 단어에 '4' 대신 'N'으로 바꿔서 표현해보자. 그럼 이렇게 표현할 수 있다.
1. N - 2번 계단을 반드시 밟았을 때, 얻을 수 있는 점수의 최댓값 + N번 계단을 밟았을 때 얻을 수 있는 점수
2. N - 3번 계단을 반드시 밟았을 때, 얻을 수 있는 점수의 최댓값 + N - 1번 계단을 밟았을 때 얻을 수 있는 점수 + N번 계단을 밟았을 때 얻을 수 있는 점수
이렇게 바꿀 수가 있다. 단지, '4'대신 'N'이라는 값을 넣었을 뿐이고, 그에 맞춰서 말을 바꿨을 뿐이다.
그럼 위의 식이 다른 값에도 적용이 되는지 확인을 해보자. 다섯번째 계단으로 알아보자.
위의 식 대로라면, 다 섯번째 계단을 밟았을 때 얻을 수 있는 점수의 최댓값은 다음과 같이 2가지 경우가 발생한다.
1. 3번(N - 2) 계단을 반드시 밟았을 때, 얻을 수 있는 점수의 최댓값 + 5번(N) 계단을 밟았을 때 얻을 수 있는 점수
2. 2번(N - 3) 계단을 반드시 밟았을 때, 얻을 수 있는 점수의 최댓값 + 4번(N - 1)번 계단을 밟았을 때 얻을 수 있는 점수 +
5번(N) 계단을 밟았을 때 얻을 수 있는 점수
1번 경우를 계산해보면, 5 + 5 = 10 이 되고 , 2번 경우는 3 + 4 + 5 = 12 가 된다.
실제로 5번 계단을 밟을 수 있는 경우를 적어보면
1. 시작점 → 1번계단 → 2번계단 → 4번계단 → 5번계단 : 1 + 2 + 4 + 5 = 12
2. 시작점 → 1번계단 → 3번계단 → 5번계단 : 1 + 3 + 5 = 9
3. 시작점 → 2번계단 → 3번계단 → 5번계단 : 2 + 3 + 5 = 10
4. 시작점 → 2번계단 → 4번계단 → 5번계단 : 2 + 4 + 5 = 11
이렇게 4가지 경우가 존재한다. 그 중 최댓값은 12가 된다. 즉 ! 위의 경우를 다 적어보면 4가지 경우가 나오고, N값이 커질수록 더 많은 경우가 발생할 수 있다. 그런데 ! 위에서 했던 과정처럼 중복된 과정 혹은 같은 결과값을 가지는 과정들이 포함되어 있을 것이고 결국에는 위에서 언급한 2가지 경우로 줄여낼 수 있다. 그리고 그 2가지 경우 중 더 큰 값이 최댓값이 된다.
실제로 다섯 번째 계단에서 검토를 해보니, 4가지 경우든, 2가지 경우든 결과값이 '12'로 동일하다는 것을 확인할 수 있다.
이를 코드로 구현하기 위해서 본인은 1차원 배열 하나를 사용해서 표현해 주었다.
바로 int DP[] 라는 배열인데, DP[a] = b 의 의미는 "a번째 계단을 반드시 밟았을 때, 얻을 수 있는 점수의 최댓값은 b점입니다." 를 의미한다.
그리고 DP[N]의 값을 구하기 위해서는 2가지 경우를 비교해주면 된다.
DP[N - 2] + Stair[N]
DP[N - 3] + Stair[N - 1] + Stair[N]
여기서 'Stair'이라는 변수는 계단의 점수들을 저장해놓은 배열을 의미한다.
마지막으로 ! 우리는 모든 값들을 계산할 때, "해당 계단을 반드시 밟았을 때, 얻을 수 잇는 점수의 최댓값" 을 계산했었는데 이 이유는 "마지막 도착 계단은 반드시 밟아야 한다" 라는 조건 때문이다. 즉 ! 우리는 위의 점화식을 토대로 값을 구한 후 최종적으로 DP[N]의 값을 출력을 하면 그 값이 정답이 된다.
[ 소스코드 ]
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 | #include <iostream> #define endl "\n" #define MAX 310 using namespace std; int N; int Stair[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 >> Stair[i]; } void Solution() { DP[1] = Stair[1]; DP[2] = Stair[1] + Stair[2]; for (int i = 3; i <= N; i++) { DP[i] = Max(DP[i - 3] + Stair[i] + Stair[i - 1], DP[i - 2] + Stair[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 |
'[ BOJ Code ] > # BOJ -' 카테고리의 다른 글
[ 백준 2156 ] 포도주 시식 (C++) (11) | 2020.07.29 |
---|---|
[ 백준 1932 ] 정수삼각형 (C++) (0) | 2020.07.29 |
[ 백준 1149 ] RGB 거리 (C++) (1) | 2020.07.24 |
[ 백준 11726 ] 2xn 타일링 (C++) (0) | 2020.07.23 |
[ 백준 1003 ] 피보나치 함수 (C++) (0) | 2020.07.22 |