[C++] 동전 1 (2293번)

동전 1

 
 
시간 제한메모리 제한제출정답맞힌 사람정답 비율
0.5 초 (추가 시간 없음) 4 MB 53544 24589 18580 45.903%

문제

n가지 종류의 동전이 있다. 각각의 동전이 나타내는 가치는 다르다. 이 동전을 적당히 사용해서, 그 가치의 합이 k원이 되도록 하고 싶다. 그 경우의 수를 구하시오. 각각의 동전은 몇 개라도 사용할 수 있다.

사용한 동전의 구성이 같은데, 순서만 다른 것은 같은 경우이다.

입력

첫째 줄에 n, k가 주어진다. (1 ≤ n ≤ 100, 1 ≤ k ≤ 10,000) 다음 n개의 줄에는 각각의 동전의 가치가 주어진다. 동전의 가치는 100,000보다 작거나 같은 자연수이다.

출력

첫째 줄에 경우의 수를 출력한다. 경우의 수는 231보다 작다.


문제풀이

 

역시 dp문제는 악마다. 코드는 굉장히 짧은데 그 과정을 이해하는 것이 매우 매우 버겁다.

처음 풀이

[101][10001] 짜리 배열을 만들어 풀어보려고 했다.

그런데 문제의 메모리 제한이 4MB라서 저 배열을 만들면 이미 사용하는 메모리가 8*101*10001 byte 이므로 8MB가 되어 메모리 초과가 뜨게 된다.

그래도 어떤 생각을 갖고 구현했는지 적어본다.

 

각 배열의 값은 어떤 식이냐면, 행은 입력받은 동전의 가치를 나타내고, 10001은 현재까지 사용한 금액을 나타낸다.

이 배열 안에는 경우의 수가 들어가게 된다.

 

글로 설명하려니 무지무지 어렵다.

점화식은 다음과 같다.

dp[i][j] = dp[i][j] + dp[i-1][j]

만약 j 가 동전의 가치보다 크다면

dp[i][j] = dp[i][j] + dp[i-1][j] + dp[i][j-동전의가치]

 

자기 자신 + 자기 이전 동전의 가치 인덱스에 있는 값 + 자기 자신 - 동전의 가치 인덱스에 있는 값

 

그러니까 지금 현재 경우의 수를 구하려면 현재 자기자신 (자기 자신이 1인 경우가 존재하므로)에 자기자신에 자기 자신 코인 가치를 뺀 값 (ex 현재 2원 동전이고 4원일 경우, 2원 동전을 2개쓰는 경우를 가져와야 함. 즉 2원동전을 1개 쓰는 인덱스에서 가져와야 함)에다가 자기 이전 동전 가치의 값을 더하면 현재 경우의 수를 구할 수 있다 (말 그대로 자기 이전 동전 사용한거의 경우의 수임).

 

  1 2 3 4 5 6 7 8 9 10
1 1 1 1 1 1 1 1 1 1 1
2 1 2 2 3 3 4 4 5 5 6
5 1 2 2 3 4 5 6 7 8 10

 

#include <iostream>

using namespace std;

int dp[101][10001];

int main() {
    int n, k;
    cin >> n >> k;
    int arr[n];
    for(int i=0; i<n; i++) {
        cin >> arr[i];
        dp[arr[i]][arr[i]] = 1;
    }

    for(int i=0; i<n; i++) {
        for(int j=1; j<=k; j++) {
            cout << dp[arr[i]][j] << " ";
        }
        cout << endl;
    }
    
    int before = 0;
    for(int i=0; i<n; i++) {
        for(int j=1; j<=k; j++) {
            if(j-arr[i]>0) {
                dp[arr[i]][j] = dp[arr[i]][j] + dp[arr[i]][j-arr[i]] + dp[before][j];
            }
            else {
                dp[arr[i]][j] = dp[arr[i]][j] + dp[before][j];
            }
        }
        before = arr[i];
    }

    // for(int i=0; i<n; i++) {
    //     for(int j=1; j<=k; j++) {
    //         cout << dp[arr[i]][j] << " ";
    //     }
    //     cout << endl;
    // }
    cout << dp[arr[n-1]][k];

    return 0;
}

 

그런데 메모리 초과이기 떄문에 이걸 어떻게 해결할지 고민해봤다.

생각해보니 이런 방식이면 하나의 배열로 모두 처리가 가능할거라고 생각하여 101 부분을 빼주고 코드를 조금 수정했더니 성공이 나왔다.

여담이지만 내 기억으론 다익스트라나 플로이드 알고리즘도 이와같이 덮어쓰기 방식으로 구현하면 메모리를 상당히 아낄수 있다고 들었다.

 

#include <iostream>

using namespace std;

int dp[10001];

int main() {
    int n, k;
    cin >> n >> k;
    int arr[n];
    for(int i=0; i<n; i++) {
        cin >> arr[i];
    }
    
    for(int i=0; i<n; i++) {
        for(int j=1; j<=k; j++) {
            if(j == arr[i]) dp[j] += 1;

            if(j-arr[i]>0) {
                dp[j] = dp[j] + dp[j-arr[i]];
            }
        }
    }
    cout << dp[k];

    return 0;
}

'알고리즘(Algorithm)' 카테고리의 다른 글

[C++] 미세먼지 안녕! (17144번)  (3) 2023.06.02
[C++] 숫자 야구 (2503번)  (0) 2023.05.15
[C++] 스도쿠 (2580번)  (0) 2023.05.02
[C++] 후보 추천하기 (1713번)  (0) 2023.04.28
[C++] 나이트 투어 (1331번)  (0) 2023.04.16