Loading [MathJax]/jax/output/HTML-CSS/jax.js
본문 바로가기

algorithm/백준알고리즘

[백준알고리즘] 12865번: 평범한 배낭 -C++

728x90

[백준알고리즘] 12865번: 평범한 배낭 -C++

12865번: 평범한 배낭 (acmicpc.net)

 

12865번: 평범한 배낭

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

www.acmicpc.net

대표적인 Knapsack 알고리즘의 문제다. 조합의 문제고, 동적계획법(DP; Dynamic Programming)을 사용하는 대표적인 문제다. 아래의 두 링크는 위키피디아인데 영어가 못 알아먹겠어도 내용이 많다. 잘 정리된 블로그들도 많으니 참고하면 좋을 듯하다.

Knapsack problem - Wikipedia

 

Knapsack problem

From Wikipedia, the free encyclopedia Jump to navigation Jump to search Problem in combinatorial optimization Example of a one-dimensional (constraint) knapsack problem: which boxes should be chosen to maximize the amount of money while still keeping the o

en.wikipedia.org

배낭 문제 - 위키백과, 우리 모두의 백과사전 (wikipedia.org)

 

배낭 문제 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 배낭 문제(Knapsack Problem 냅색 프라블럼[*])는 조합 최적화의 유명한 문제이다. 간단하게 말하면, 한 여행가가 가지고 가는 배낭에 담을 수 있는 무게의 최댓값이

ko.wikipedia.org

 

이러한 Knapsack 문제는 두 가지 유형이 있다. 가치 있는 물건을 잘라서라도 가져갈 수 있는 Fractional Knapsack과 물건을 절대 자를 수 없는 0-1 Knapsack이다.

 

Fractional Knapsack의 경우에는 쉽다. 가장 가치가 높은 물건부터 최대한 꾹꾹 눌러 담아가면 된다. 그렇기 때문에 0-1 Knapsack이 상대적으로 DP를 공부하기에 적절하고 더 유명하다.

 

아래는 예전에 파이썬으로 풀었을 때의 블로그 글이다. 내 블로그의 첫 글이자 어쩌면 글 하나 작성하는데 가장 오래 걸렸던 문제다. 나름 HTML도 모르는데 표도 넣으려고 찾아가면서 했었다. 오늘은 이만큼 자세히는 풀지 않을 것 같다.

[백준알고리즘] 12865번: 평범한 배낭 -Python (tistory.com)

 

[백준알고리즘] 12865번: 평범한 배낭 -Python

[백준알고리즘] 12865번: 평범한 배낭 -Python https://www.acmicpc.net/problem/12865 12865번: 평범한 배낭 첫 줄에 물품의 수 N(1 ≤ N ≤ 100)과 준서가 버틸 수 있는 무게 K(1 ≤ K ≤ 100,000)가 주어진..

suri78.tistory.com

 

잘못된 점이나 부족한 점 지적해주시면 감사하겠습니다

 


 

시작한 아이디어는 간단하다. 시간제한이 2초로 넉넉한 덕분에 대략 2억 번이라는 충분하고도 남아도는 연산 횟수가 있었기 때문이다. 

생각했던 아이디어는 아래와 같다.

  • 우선 무게순으로 물품들을 정렬한다.
  • 가방에 기존에 들어갈 수 있던 조합에서 해당 물품을 추가했을 때의 무게와 가치를 구한다.
  • 각 조합별 늘어난 총 무게에서 구할 수 있는 최대의 가치를 구한다.

 

이렇게 할 수 있었던 이유는 무게 순 정렬하는 것도 100 개의 item 밖에 없었고, 준서가 버틸 수 있는 무게가 100,000 밖에 되지 않았기 때문이다. 따라서 조합별 늘어난 무게를 각각 구하더라도 O(nk) 밖에 되지 않았고, 이는 10,000,000 연산 밖에 되지 않기 때문이다.

 

그래서 위의 아이디어를 반복하면서 넣을 수 있는 물건들의 조합에서 최대 가치를 구했다.

 

다만, 글 맨 아래 코드에서 knapsack 함수의 이중 for문의 안쪽 for문에서 i를 감소하면서 접근했다. i를 증가하면서 접근하면 다음과 같은 문제가 생기기 때문이다.

item weight : 1, item value : 2

i = 0
max values index : 0 1 2 3 4 5 6 ...
max values         : 0 2 0 0 0 0 0 ...

i = 1
max values index : 0 1 2 3 4 5 6 ...
max values         : 0 2 4 0 0 0 0 ...

i = 2
max values index : 0 1 2 3 4 5 6 ...
max values         : 0 2 4 6 0 0 0 ...

...

 

즉, 갱신되지 말아야 할 무게들이 갱신되는 문제가 생긴다. 무게가 1이고 가치가 2인 물품은 하나인데, 해당 물품을 여러 번 챙기는 것과 같은 것이다. 이러한 문제를 해결하기 위해서 큰 무게에서 작은 무게로 i를 감소하면서 접근했다.

 

아래는 위의 로직대로 푼 C++ 코드다. sort는 항상 헷갈리는 것 같다. vector냐 어디냐에 따라서 계속 달라지니.. vector에서 오름차순은 세 번째 인자로 std::less<>()를 넣는 것과 같다.

#include <iostream>
#include <vector>
#include <algorithm>

void solve(void);

int main(void)
{
	std::ios::sync_with_stdio(false); 
	std::cin.tie(NULL); 
	std::cout.tie(NULL); 
	
	solve();
}

int knapsack(std::vector<std::pair<int, int>> pairsWeightValue, int maxWeight)
{
	std::vector<int> maxValues(maxWeight + 1, 0);

	for (auto& pairWeightValue : pairsWeightValue)
	{
		int& weight = pairWeightValue.first;
		int& value = pairWeightValue.second;

		for (int i = maxWeight - weight; i >= 0; i--)
		{
			if (0 < i && maxValues[i] == 0)
				continue;

			maxValues[i + weight] = std::max(maxValues[i] + value, maxValues[i + weight]);
		}
	}

	return *std::max_element(maxValues.begin(), maxValues.end());
}

void solve(void)
{
	int n, k;
	std::cin >> n >> k;

	std::vector<std::pair<int, int>> objects(n);
	for (auto& object : objects)
		std::cin >> object.first >> object.second;

	//std::sort(objects.begin(), objects.end(), std::less<>());
	std::sort(objects.begin(), objects.end());
	std::cout << knapsack(objects, k);
}
728x90