호우동의 개발일지

Today :

article thumbnail

문제 이해 단계


N개의 돌이 있고 마지막 돌에 도착해야 한다.


1 ~ N-1개의 돌에 정보가 주어지는데
특이한 점은 작은 점프/큰 점프가 드는 에너지가 다르다.


그리고 엄청 큰 점프는 딱 한 번만 가능하다.


이러한 조건에서 에너지를 최소한만 들여 마지막 돌에 도착해야 한다.

 

 

 

 

 

문제 접근 단계


 

전형적인 DP문제인지는 모르겠지만,
이런 DP문제를 전에 많이 풀어봐서 내 눈에는 DP문제인지 바로 보였다.


왜냐하면 Top-Bottom 방식으로 생각해 보면
구조가 보이기 때문이다.


그림으로 한번 살펴보자.

돌이 나열되어 있고,
중간에 아무런 돌 X(파란색)를 잡는다고 생각해 보자.



그렇다면 돌 X에 도달할 수 있는 경우는 어떻게 나올까?

돌 x에 도달할 수 있는 경우

 

이런 식으로 총 3가지 경우밖에 나오지 않을 것이다.


확대해서 말하면,


모든 돌에 관해서
이 3가지 경우밖에 나오지 않기 때문에 일반화가 가능하다
는 소리이다.


그렇기 때문에 이를 일반화하여 점화식을 세우면
쉽게 구할 수 있기 때문에 이는 DP문제이다.

문제 조건에 유의하면서 점화식을 한번 세워보자.


여기서 유의해야 할 점은
엄청 큰 점프는 딱 한 번만 가능하다는 점이다.


이를 돌 X라는 시점에서 보면 돌 X일 때는
총 3가지 상태로 나타날 수 있다.


1. 이미 앞에서 엄청 큰 점프를 한 경우
2. 돌 X가 엄청 큰 점프를 할 경우
3. 돌 X가 엄청 큰 점프를 하지 않을 경우


이를 다시 두 가지 상태로 구분하면
돌 X 전후로 엄청 큰 점프를 사용했냐의 유무로 판단할 수 있다.


이제 위에서 정리했던 것들을 점화식으로 나타내보겠다.

 

DP [x][a]가 의미하는 것은 돌 X까지 오는데 드는 최소한의 에너지이다.



DP [x][0]과 Dp [x][1] 이렇게 나타낼 것인데,
두 번째 인덱스 0,1은 엄청 큰 점프의 사용유무이다.



1이면 돌 X 이전에 엄청 큰 점프를 이미 사용한 것이고
0이면 아직 사용하지 않은 것이다.

먼저 X 돌 이전에 엄청 큰 점프를
사용하지 않았을 경우의 점화식은 아래와 같다.


DP [X][0]
=
MIN
((X-1번째 돌의 작은 점프 에너지) + DP [X-1][0]),
((X-2번째
돌의 큰 점프 에너지) + DP [X-2][0])



왜냐하면 엄청 큰 점프를 사용하지 않았단 것은
(X-1), (X-2)에서 밖에 오지 못하기 때문이다.


엄청 큰 점프를 사용하지 않았기 때문에
사용하는 DP는 두 번째 인덱스가 0인 DP배열이다.

두 번째로 X돌 이전의 엄청 큰 점프를 사용하거나,
X돌에서 점프를 사용할 경우이다.


DP [X][1]
=
MIN
((X-1번째 돌의 작은 점프 에너지) + DP [X-1][1])
,((X-2번째 돌의 큰 점프 에너지)+DP [X-2][1]), K+DP [X-3][0])


이런 점화식이 나오게 된다.


첫 번째와 두 번째 식은 점프를 사용한 상태이기 때문에
두 번째 인덱스가 1이고,


세 번째 식은 지금 엄청 큰 점프를 사용하기 때문에
이전에는 엄청 큰 점프를 사용하지 않았다.


그렇기 때문에 두 번째 인덱스가 0인 DP를 이용한다.

N돌에 도착한 최소 에너지 DP [N][0]과
DP [N][1] 중 더 작은 것이 답이 된다.




이제 이걸 코드로 구현하면 된다.

 

 

 

 

문제 구현 단계


#include <iostream>
#include <utility>
using namespace std;

#define MAX 999999;
int d[21][2];
pair<int,int> v[21];
int main(){
    cin.tie(0); cout.tie(0); ios::sync_with_stdio(false);
    int n,k;
    cin >> n;

    for(int i = 1; i <n; i++){
        int v1,v2;
        cin >> v1 >> v2;
        v[i] = make_pair(v1,v2);
    }
    cin >> k;
    for(int i = 0; i<= n; i++) {
        d[i][0] = MAX;
        d[i][1] = MAX;
    }
    d[1][0] = 0;
    d[2][0]= v[1].first;
    d[3][0]= min(v[1].first+v[2].first,v[1].second);
    for(int i = 4; i <= n; i++){
        d[i][0] = min(v[i-1].first + d[i-1][0],v[i-2].second+d[i-2][0]);
        d[i][1] = min(min(v[i-1].first + d[i-1][1],v[i-2].second+d[i-2][1]),k+d[i-3][0]);
    }
    cout << min(d[n][0],d[n][1]);
}

DP 배열을 담아줄 2차원 배열 d와,
작은 점프와 큰 점프의 에너지를 담을 v를 pair로 정의했다.


그리고 min값을 구해야 하기 때문에
MAX를 정의하여 오류를 방지하였다.

일단 모든 d배열을 MAX로 초기화한 후
반복문은 4부터 시작(i-3 때문에) 하기 때문에
d배열의 1~3은 손수 초기화 해준다.


그 후 4부터 순차적으로 n까지 점화식을 적용시켜 준 후
d [n][0]과 d [n][1]중 더 작은 것을 출력한다.

 

 

 

 

시행착오


DP로 푸는 문제임과
어떤 식으로 접근해야 하는지는 바로 알아차려서 금방 풀 줄 알았다.



근데 처음에는 "엄청 큰 점프는 단 한번"을
DP로 어떻게 구현해야 할지 애를 먹다가
예제 케이스를 아무리 넣어봐도 맞왜틀이 떠서 고생하고,



재귀함수로도 바꿔보고, 반복문으로도 해보고

d를 2차원배열, 1차원배열, 두 번째 인덱스를 3개씩 만들어보고
방법이란 방법은 다 써본 것 같다.

그런데도 결과는 계속 같았다.


그래서 결국 더 이상 시간 낭비는
무의미하다고 생각해서 인터넷을 참고하였다.



위에 풀이처럼 큰 점프를 사용하는 것과
사용하지 않는 배열로 두 가지로 나눈 것으로 비슷하게 접근했지만

그 이후의 생각까지는 미치지 못했던 것 같다.



단 한 번의 점프라는 것에 꽂혀서 기본적인 것을 많이 놓친 것 같다.