C++/DS, Algorithm

Knapsack Problem

728x90

- DP 와 Knapsack Problem

: 배낭 문제는, 어떤 한 사람이 갖고 있는 배낭이 있고, 그 배낭에 담을 수 있는 최대 용량이 주어지며, 이 최대 용량에 한해서, 여러개의 물건들을 집어넣고자 할때, 최대한의 가치를 뽑아내는 방법을 찾는 문제이다.

각 물건들은 무게와 값어치가 명시되어 있고 이들 중에서 배낭에 넣을 수 있으면서, 최대의 값어치를 뽑아내는 경우의 수를 찾아내면 되는 문제이다.

배낭 문제는 다시 말하면 '여러 물건이 있을때, 특정한 조건을 만족하는 조합을 구하는 문제' 라고 볼 수 있다.

 

배낭 문제는 두가지 유형으로 나뉜다.

물건(짐) 을 쪼갤 수 있는 경우와 물건을 쪼갤 수 없는 경우의 두 유형으로 나뉘는데,

전자의 경우 '분할가능 배낭문제' (Fractional Knapsack Problem) 라고 부르고 

후자의 경우 '0-1 배낭 문제' (0-1 Knapsack Problem) 라고 부른다

또한 전자의 경우 그리디로 다항 시간안에 풀 수 있으나

후자의 경우 보통 DP 를 이용한다

 

알고리즘 문제를 풀때 보통 DP 로 해결해야 되는 경우가 많다.

 

아래는 가장 대표적인 배낭 문제 (백준 12865 - 평범한 배낭) 이다.

www.acmicpc.net/problem/12865

 

12865번: 평범한 배낭

첫 줄에 물품의 수 N(1 ≤ N ≤ 100)과 준서가 버틸 수 있는 무게 K(1 ≤ K ≤ 100,000)가 주어진다. 두 번째 줄부터 N개의 줄에 거쳐 각 물건의 무게 W(1 ≤ W ≤ 100,000)와 해당 물건의 가치 V(0 ≤ V ≤ 1,000)

www.acmicpc.net

 

이 문제를 풀기위한 DP 2차원 배열은 다음과 같이 정의될 수 있다.

 

DP[i][j] : 처음부터 i번째까지 물건들을 살펴보고, 배낭의 용량이 j 일때 배낭에 들어간 물건들의 가치의 합의 최대값

 

그러므로 문제에서 찾으라는 답은 DP[N][K] 에 있을것이라 볼 수 있다.

DP[N][K] 의 의미가 처음 물건부터 마지막 N 번째 물건들 까지 살펴보고, 배낭의 용량이 K 일때, 배낭에 들어간 물건들의 가치의 합의 최대값이 되기 때문이다.

 

이때 DP 배열에 대한 점화식을 

DP[i][j] = max(DP[i - 1][j], DP[i - 1][j - weight[i]] + value[i]) 으로 생각할 수 있다

 

이 점화식의 의미는, 처음부터 i - 1 번째 까지의 물건들을 살펴보고 배냥용량이 j 일때 배낭에 들어간 물건들의 가치의 합의 최대값 과 처음부터 i - 1 번째 까지의 물건들을 살펴보고, i 번째 물건을 배낭에 넣었을때 가치의합의 최대값 들 중 큰값을 dp[i][j] 에 넣는다 가 된다.

 

i 번째 물건의 무게는 weight[i] 이며, i 번째 물건의 가치는 value[i] 이다.

 

배낭 문제는 이것처럼 i번째 물건을 넣었을때와 안넣었을때 가치의합의 최댓값을 골라내면 되는 그런 방식으로 접근하면 된다.

 

그래서 이거를 코드로 작성하면 아래와 같이 쓸 수 있다.

 

- C++

#include <iostream>
 
#define MAX 101
#define MAX_K 100001
 
using namespace std;
 
int N, K;
int weight[MAX], value[MAX];
int dp[MAX][MAX_K];
 
int main() {
    cin.tie(0); ios_base::sync_with_stdio(0);
    
    cin >> N >> K;
    for (int i = 1; i <= N; ++i) 
        cin >> weight[i] >> value[i];
        
    for (int i = 1; i <= N; ++i) {
        for (int j = 1; j <= K; ++j) {
            if (j - weight[i] >= 0) {
                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
            } else {
                dp[i][j] = dp[i - 1][j];
            }
        }
    }
    
    cout << dp[N][K];
    
    return 0;
}
cs

else 부분은 배낭에 i 번째 물건을 넣을 수 없는 경우, 배낭의 용량 j 는 그대로 두고 i 번째 물건은 넣지 않은 값 

dp[i - 1][j] 로 그대로 넣는다.

 

여기서 한단계 더 생각해서 2차원 배열 대신 1차원 배열로 처리할 수 도 있다.

 

위의 코드는 배낭의 용량을 j = 1 ~ j = k 까지 오름 차순으로 돌려보는 방식이라면, 

이번에는 역으로 j = k  ~ j = 1 으로 내림차순으로 돌려보는건 어떨지 생각해보면 된다.

 

문제의 예제 입력 1 을 기준으로 생각해보자

 

N = 4, K = 7 일 때 다음과 같이 입력이 들어온다. 

  weight[i] value[i]
1번 물건 6 13
2번 물건 4 8
3번 물건 3 6
4번 물건 5 12

 

이때 1번 물건을 넣는다 치면, 배낭의 용량을 내림차순으로 돌려본다 했으므로

 

- 1번 물건 

j = 7 일때) dp[7] = max(dp[7], dp[7 - weigth[1]] + value[1])

j = 6 일때) dp[6] = max(dp[6], dp[6 - weight[1]] + value[1])

j = 5 일때) dp[5] = max(dp[5], dp[5 - weight[1]] + value[1]) 

......

 

1번 물건을 넣고 난 후, dp 테이블은 아래와 같이 된다.

  1 2 3 4 5 6 7
1번물건 0 0 0 0 0 13 13

 

 

다음으로 2번 물건을 넣는다치면

 

- 2번 물건

j = 7 일때) dp[7] = max(dp[7], dp[7 - weight[2]] + value[2])

j = 6 일때) dp[6] = max(dp[6], dp[6 - weight[2]] + value[2])

j = 5 일때) dp[5] = max(dp[5], dp[5 - weight[2]] + value[2])

......

 

2번 물건을 넣고 난 후, dp 테이블은 다음과 같이 갱신된다.

  1 2 3 4 5 6 7
2번물건 0 0 0 8 8 13 13

 

이런식으로 쭉 4개의 물건 다 해보면 최종적으로는 아래 처럼 된다

 

  1 2 3 4 5 6 7
최종 0 0 6 8 12 13 14

 

따라서 답이 14가 된다.

 

- C++

#include <iostream>
 
using namespace std;
 
int N, K;
int dp[100001];
int weight[101], value[101];
 
int main() {
    cin.tie(0); ios_base::sync_with_stdio(0);
    
    cin >> N >> K;
    
    for (int i = 1; i <= N; ++i) cin >> weight[i] >> value[i];
    
    for (int i = 1; i <= N; ++i) 
        for (int j = K; j >= 1--j) 
            if (j - weight[i] >= 0)
                dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    
    cout << dp[K];
    
    return 0;
}
cs

 

아래의 백준 결과표를 보면 상당한 차이를 가져옴을 볼 수 있다.

 

 

 

 

- 다른 냅색 문제들

1) BOJ 7579 - 앱 : www.acmicpc.net/problem/7579

2) BOJ 1943 - 동전 분배 : www.acmicpc.net/problem/1943

 

 

 

- References)

1. chanhuiseok.github.io/posts/improve-6/

2. ko.wikipedia.org/wiki/%EB%B0%B0%EB%82%AD_%EB%AC%B8%EC%A0%9C

728x90