반응형
문제 설명
 

algospot.com :: PACKING

여행 짐 싸기 문제 정보 문제 여행을 떠나기 전날까지 절대 짐을 싸지 않는 버릇이 있는 재훈이는 오늘도 비행기 타기 전날에야 가방을 싸기 위해 자리에 앉았습니다. 비행기 규정상 재훈이는

www.algospot.com

  • 캐리어의 용량 w (1 <= w <= 1000)가 있다.
  • 캐리어에 넣고자 하는 물건 n(1 <= n <= 100)개가 주어진다.
  • 각 물건의 부피(1 <= 부피 <= 1000)와 절박도(1 <= 절박도 <= 1000)가 주어진다.

=> 캐리어의 용량이 넘지 않게 물건을 넣을 때, 얻을 수 있는 최대 절박도 합과 가져갈 물건의 개수, 각 물건의 이름 출력하기

 

 

문제 풀이

Knapsack 문제

 

이 문제는 knapsack이라는 dp를 사용하는 대표 문제로 잘 알려져 있다.

 

dp에서 가장 중요한 것은 sub problem으로 문제를 쪼개는 것인데,

이 문제는 작게 봤을 때 한 물건에 대하여 넣기 or 넣지 않기 로 선택할 수 있다.

그리고 물건을 넣을 수 있는 경우는 캐리어의 용량이 해당 물건보다 클 때 넣을 수 있다.

 

물건의 개수(k)와 캐리어의 용량(W) 크기로 이루어진 2차원 배열(dp)를 만들고 다음과 같이 배열을 채울 수 있다.

 

i) W용량인 캐리어에 넣을 수 있을 때

dp[k][W] = dp[k-1][W-wk] + k의 만족도

ii) W용량인 캐리어에 넣을 수 없을 때

dp[k][W] = dp[k-1][W]

 

두 경우 중 큰 만족도를 가지는 것을 선택한다.

 

DP의 공간 줄이기

 

 

위의 코드를 보면 물건 k에 대해서 k-1행의 정보를 활용하여 k행을 채운다.

즉, 2행짜리 배열을 만든 후, 매 물건마다 최대 만족도를 구하고 윗줄로 한 칸씩 옮겨서 계산을 반복해준다면

k x W 크기가 아닌 2 x W 크기의 배열로 원하는 답을 구할 수 있다.

 

여기에 더해, 1줄짜리 배열로도 dp를 활용할 수 있다.

바로 연산 방향을 0 -> W가 아닌, W -> 0 의 역방향으로 연산해주면 된다.

이게 가능한 이유는 W용량인 캐리어에 대해 값을 알고 싶을 때, W보다 작거나 같은 열의 값을 이용하기 때문이다.

물건 k에 대하여

i) W용량인 캐리어에 넣을 수 있을 때

dp[W] = dp[W-wk] + k의 만족도

ii) W용량인 캐리어에 넣을 수 없을 때

dp[W] = dp[W]

임을 활용하면 1줄짜리 배열로 dp를 구성할 수 있다.

 

문제 풀이 코드
/*
	가져갈 수 있는 물건의 조합 중,
	무게 합이 무게 제한을 넘지 않는 묶음 중,
	그 중 절박도가 제일 높은 묶음

	Knapsack 문제
	1000 * 100은 공간 초과될 것 같으므로 2줄짜리로 구하자.

	역방향 dp는 한줄로 가능

*/

#include <iostream>
#include <string.h>

using namespace std;

struct Object {
	char objectName[21];
	int weight;
	int need;
};

Object objects[100];
int N, W;
int dp[1001];				// 무게에 따른 선호도
int objectCnt[1001];		// 무게에 따른 물건 개수
int objectIndexList[1001][100];		// 무게에 따른 물건 index 목록

void makeDP(int objectIndex) {
	Object ob = objects[objectIndex];
	for (int i = W; i > 0; --i) {
		if (ob.weight <= i) {
			if (dp[i] < dp[i - ob.weight] + ob.need) {
				dp[i] = dp[i - ob.weight] + ob.need;
				memcpy(objectIndexList[i], objectIndexList[i - ob.weight], sizeof(int) * 100);
				objectIndexList[i][objectCnt[i - ob.weight]] = objectIndex;
				objectCnt[i] = objectCnt[i - ob.weight] + 1;
			}
		}
	}
}

int main()
{
	int C;
	cin >> C;
	for (int tc = 1; tc <= C; ++tc) {
		cin >> N >> W;
		for (int i = 0; i < N; ++i) {
			cin >> objects[i].objectName >> objects[i].weight >> objects[i].need;
		}
		memset(dp, 0, sizeof(dp));
		memset(objectCnt, 0, sizeof(objectCnt));
		for (int i = 0; i < N; ++i) {	// 각 물건을 넣을지 말지에 대한 연산
			makeDP(i);
		}
		cout << dp[W] << " " << objectCnt[W] << endl;
		for (int i = 0; i < objectCnt[W]; ++i) {
			cout << objects[objectIndexList[W][i]].objectName << endl;
		}
	}
	return 0;
}

 

반응형