이 문제는 동적계획법(DP)으로 풀 수 있는 문제이다. 그 이유는 완전한 알약 갯수와 반쪽 짜리 알약 갯수가 주어질 때, 문자열의 경우의 수는 일정하기 때문이다. 따라서 DP는 다음과 같이 정의 할  수 있다.

dp[w][h] = 완전한 알약이 w 개 있고,  반쪽 짜리 알약이 h 개 있을 때, 문자열의 경우의 수

dp[w][h] = dp[w-1][h+1]  +  dp[w][h-1]

 

 

 

Top-Down

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef long long ll;
ll dp[33][66];
 
ll solve(int w, int h){
    if(w == 0)
        return 1;
    
    ll& ret = dp[w][h];
    if(ret != 0)
        return ret;
    
    ll s = solve(w-1, h+1);
    if(h > 0)
        s = s + solve(w, h-1);
    return ret = s;
}
cs

 

 

Bottom-Up

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef long long ll;
ll dp[33][66];
 
// 초기화    
for(int h=1; h<=60;h++)
    dp[0][h] = 1;
dp[1][0= 1;
 
for(int w=1; w<=30; w++){
    for(int h=0; h<=60; h++){
        dp[w][h] = dp[w-1][h+1+ (h > 0 ? dp[w][h-1] : 0);
    }
}
cs

이 문제는 초기화만 잘 해준다면 Top-Down 방식과 코드가 큰 차이가 없이 Bottom-Up 또한 꽤 직관적인 코드가 짜여진다.

 

 

 

전체 소스 코드

 

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
#include<stdio.h>
 
int n;
typedef long long ll;
ll dp[33][66];
ll inline  max(ll a, ll b){return a>b?a:b;}
ll solve(int w, int h){
    if(w == 0)
        return 1;
    
    ll& ret = dp[w][h];
    if(ret != 0)
        return ret;
    
    ll s = solve(w-1, h+1);
    if(h > 0)
        s = s + solve(w, h-1);
    return ret = s;
}
int main(){
    // solve(30, 0);
    
    for(int h=1; h<=60;h++)
        dp[0][h] = 1;
    
    dp[1][0= 1;
    for(int w=1; w<=30; w++){
        for(int h=0; h<=60; h++){
            dp[w][h] = dp[w-1][h+1+ (h > 0 ? dp[w][h-1] : 0);
        }
    }
 
    while(1){
        scanf("%d",&n);
        if(n==0break;
        printf("%lld\n", dp[n][0]);
    }
    return 0;
}
cs

 

 

 

 

 

 

'알고리즘 문제풀이' 카테고리의 다른 글

LCS 최장 공통 부분 수열  (0) 2020.09.03
BOJ 가장 긴 증가하는 부분 수열  (0) 2020.09.02
BOJ 12865 평범한 배낭  (0) 2020.08.28
BOJ 15649~15650 N과 M  (0) 2020.08.20
BOJ 2580 스도쿠  (0) 2020.08.20

 

이 문제는 다이나믹 프로그래밍(동적계획법, DP) 문제이다. 나는 평소에 Top-down 방식으로 구현하는 것이 익숙해, bottom-up 방식의 구현을 잘 구현하지 못한다.. 그래서 bottom-up 방식으로도 구현하는 연습해야겠다고 생각해서 오랜만에 DP 문제를 찾았다.

 

DP 는 큰 문제를 작은 문제로 나누어푸는데, 분할정복과 다른 점은 DP는 작은 문제로 나누어 풀 때, 작은 문제들의 결과가 항상 동일하고 반복이 일어난다. 작은 문제들의 일정한 결과를 이용해 큰 문제를 해결하는 과정이라고 볼 수 있다. 따라서, 작은 문제들의 일정한 결과 값을 저장하기 위해 Memoization 을 하며, 같은 작은 문제를 만났을 때 이미 한 번 해결했던 문제라면 저장해놓은 값을 이용해 반복 수행 횟수를 줄일 수 있다.

 

Top-Down

 

먼저  Top-Down 방식의 풀이에서는, DP를 다음과 같이 정의한다.

dp[p][q] = q 만큼의 무게를 들 수 있을 때,  p 번째의 물건을 선택해 얻을 수 있는 최대의 가치

이렇게 정의하면, 

dp[p][q] = max( dp[p-1][q] ,  dp[p-1][ q - weight[p] ] + value[p] )

처럼 p 번째 물건을 선택하지 않은 경우와 선택한 경우 모두 고려할 수 있다.

 

기저조건(재귀의 탈출조건) 은 p 번째 물건이 0~n-1 번의 물건 개수 범위를 넘어설 때이다. Top-down 방식의 구현은 다음과 같다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
int solve(int p, int q){
    if(p == -1return 0;
 
    int& ret = dp[p][q];
    if(ret != 0)
        return ret;
    
    int h = solve(p-1, q);
    if(q - arr[p][0>= 0)
        h = max( h, solve(p-1, q-arr[p][0]) + arr[p][1] );
 
    return ret = h;
}
cs

 

 

Bottom-Up

 

다음은 반복문을 이용해 구현하는 Bottom-Up 방법으로 풀어보면, 마찬가지로 2차원의 배열이 필요하다. 익숙치 않아서 시간이 꽤 걸렸다.

DP의 정의는 같으며, 내가 구현한 방식은 다음과 같다.

 

1
2
3
4
5
6
7
8
9
10
    for(int i=1;i<=n;i++){
        dp[i][arr[i-1][0]] = arr[i-1][1];
        for(int w=0; w<=k; w++){
            dp[i][w] = dp[i-1][w];
            if(w - arr[i-1][0>= 0){
                dp[i][w] = max(dp[i][w], dp[i-1][w-arr[i-1][0]] + arr[i-1][1]);
            }
        }
    }
    printf("%d\n", dp[n][k]);
cs

 

위 문제의 예제는 너무 쉽기 때문에, 다음 예제를 이용해 설명하면 다음과 같다.

4 20
6 13
2 5
3 9
10 20

4개의 물건이 있으며 무게는 7 까지 가능하기 때문에, 배열은 dp[4][7] 크기가 필요하다.

dp 에는 i 번째 물건까지 고려했을 때, w 무게에서의 최대 가치를 저장한다. 

즉,

 

무게는 0~20 까지 모두 출력되었다.  dp[1] 은 첫 번째 물건만을 고려했을 때 까지의 가능한 최대 가치를 보여주며, dp[2]는 두 번째 물건 까지 고려했을 때의 가치이다. 두 번째 물건의 무게는 2로 인덱스 2번 부터 무게 5가 채워지며, 무게 6부터는  첫 번째 물건의 가치가 더 크기 때문에 13이 유지되며, 두 무게를 합친 8 무게부터는 두 가치를 합친 값이 저장되기 시작한다. 

 

 

전체 소스 코드

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
#include<stdio.h>
 
int n,k;
int arr[101][2], dp[101][100001];
 
int inline max(int a,int b){ return a>b? a:b;}
int solve(int p, int q){
    if(p == -1return 0;
 
    int& ret = dp[p][q];
    if(ret != 0)
        return ret;
    
    int h = solve(p-1, q);
    if(q - arr[p][0>= 0)
        h = max( h, solve(p-1, q-arr[p][0]) + arr[p][1] );
 
    return ret = h;
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i=0;i<n;i++){
        scanf("%d %d",&arr[i][0], &arr[i][1]);
    }
 
    // int ans = solve(n-1, k);
    // printf("%d\n", ans);
 
    for(int i=1;i<=n;i++){
        dp[i][arr[i-1][0]] = arr[i-1][1];
        // printf("dp[%d] : ",i);
        for(int w=0; w<=k; w++){
            dp[i][w] = dp[i-1][w];
            if(w - arr[i-1][0>= 0){
                dp[i][w] = max(dp[i][w], dp[i-1][w-arr[i-1][0]] + arr[i-1][1]);
            }
            // printf("%2d ", dp[i][w]);
        }
        // printf("\n");
    }
    printf("%d\n", dp[n][k]);
 
    return 0;
}
 
 
cs

 

'알고리즘 문제풀이' 카테고리의 다른 글

BOJ 가장 긴 증가하는 부분 수열  (0) 2020.09.02
BOJ 4811 알약  (0) 2020.08.28
BOJ 15649~15650 N과 M  (0) 2020.08.20
BOJ 2580 스도쿠  (0) 2020.08.20
BOJ 1194 달이 차오른다, 가자.  (0) 2020.08.11

+ Recent posts