HDU -- Watch The Movie(ACM Step: 3.3.4)

一,概述

1.問題描述

女主角多多,有N部想看的電影,她爺爺限制她只能看L分鐘,她將每部電影按喜好標明瞭價值V[i],同時每部電影也有自己的播放時間T[I],i from 1 to N,每部電影要麼看完要麼不看,現要求叔叔去幫她買回來,可是商店中只有賣N種中的M種,請問叔叔如何在L,M的限制下買到價值最大的物品呢?

問題抽象:

N個物品,每個物品,容量T[i],價值V[i],揹包容量L,若最多裝M件且M<=N,求如何在L,M限制下挑選出最大價值的物品組合?

2.問題鏈接

HDU -- Watch The Movie(ACM Step: 3.3.4)

3.問題截圖

圖1.1  問題截圖

二,算法思路

乍一看下,似乎是普通的“二維費用的01揹包問題”,此術語來自書籍《揹包九講》,只需要在M或者M件之內選出最大價值的物品組合即可,費用一是物品的容量(揹包最大容量爲L,每件物品若裝入,會消耗一定的揹包容量),費用二是物品的件數(揹包最多裝M件,每件物品若裝入,會消耗一件)。

上述思路實現的代碼無法得到正確的結果,於是參考了網上的思路,主要有兩種說法:

1)每件物品的價值可能是負的,所以初始化應該把某些值設爲負數(有設-1,有設負無窮的)。

2)需要求出裝滿M件的結果。

對於第一種說法,首先題目有聲明每件物品的價值是大於0的,並且如果說法一正確,那麼爲什麼要設負數呢??沒有想明白,個人覺得如果價值是負數,應該簡單的不裝這件物品,裝它只會把總價值變小。

在參考了第二個思路下,總結了兩種算法,差別在於空間複雜度,差別不大,一個常數係數,但是對思考的方式有顯著的改善,現說明如下。

1.空間複雜度O(2LM)的算法

首先,對於二維費用的01揹包問題,需要使用二維數組保存最後的結果,即可以得到的最大價值,由於題目有對件數的限制,因此最後的結果不僅記錄了價值,還記錄了當前是否裝滿的信息,這相當於用了兩個二維數組來記錄答案。所以複雜度爲O(2LM)。假設保存價值的二維數組爲F[L][M],保存裝滿信息的二維數組是G[L][M]。

具體實現思路是:初始化時,將F所有元素初始化爲0,因爲此時還沒有物品裝入;對於G,將G[L][0]初始化爲0,其他初始化爲-1,表示在最大件數爲0件時,此時無論L是多少,都可以看作是裝滿的,最大件數大於0時,-1表示裝不滿。

最後,按照求解二維費用的01揹包問題的一般思路,即方程F[l][m] = max(F[l][m], F[l-T[i]][m-1] + V[i])

只需要在求每一個最大價值時,首先判斷兩個候選表達式的G數組值,即只有在它們裝滿的情況下,才進行比較大小。

2.空間複雜度O(LM)的算法

在參考了一些作者的代碼後,並且寫完上述代碼後總感覺好像多了點什麼,《揹包九講》的關於“裝滿物品的初始化”在腦海裏一直迴響,裏面說的是“對於一開始就裝滿的狀態的初始化(通常是0),初始化爲0,對於裝不滿的狀態的初始化,初始化爲負無窮“,最後肯定了這個想法:“由於僅要求件數裝滿,所以對於物品件數的維度,在裝0件的那些狀態,初始化爲0,表示裝滿,對於其他的初始化爲負無窮“,由於題目有規定所有物品的價值和不會超過能表示的最大正數,所以可以初始化爲最大負數,這樣在全部都裝並且價值最大的情況下還是可以保持最後的結果時負數,即裝不滿,代碼的通過驗證了此想法的正確性,致敬《揹包九講》的作者,將問題分析的十分透徹。

所以在之後的裝滿問題中,若要求將某些維度裝滿,則將那些對應維度的初始化工作做好即可。

三,算法實現

#include <iostream>    // for cin, cout, endl
#include <climits>    // for INT_MIN

using std::cin;
using std::cout;
using std::endl;

const int MAX_MOVIES = 100;    // the max num of movies that can select
const int MAX_TIMES = 1000;    // the max num of time used for watching movie

void input(int&, int&, int&);
void compute(int&, int&, int&);
void output(int&, int&);

struct Movie{
	int t;    // length of time of movie
	int v;    // value of movie

};    // input data, information about movie

Movie movies[MAX_MOVIES+1];    // hold input
int ans[MAX_TIMES+1][MAX_MOVIES+1];    // +1 for time from 0 to 1000 and movie num from 0 to 100

int main()
{
	int t, M, N, L;

	cin >> t;
	for (int i=0; i<t; ++i){
		input(M, N, L);
		compute(M, N, L);
		output(M, L);
	}
}

int max2(int a, int b)
{
	if (a > b)
		return a;
	else
		return b;
}

void input(int& M, int& N, int&L)
{
	cin >> N >> M >> L;
	for (int i=1; i<=N; ++i)
		cin >> movies[i].t >> movies[i].v;
}
void compute(int& M, int& N, int& L)
{
	int m, n, t;
	// initialize the ans array, ans[t][0] to 0 and others to INT_MIN,
	// because when 0 movie be selected only the m==0 can be filled and value is 0, others can not be filled and using -INT_MIN to indication
	for (t=L; t>=0; --t)
		for (m=M; m>=0; --m)
			ans[t][m] = INT_MIN;
	for (t=0; t<=L; ++t)
		ans[t][0] = 0;

	for (n=1; n<N; ++n)    
		for (t=L; t>=movies[n].t; --t)
			for (m=M; m>=1; --m)
				ans[t][m] = max2(ans[t][m], ans[t-movies[n].t][m-1]+movies[n].v);

	// for movie N
	if (movies[N].t > L)
		return;

	ans[L][M] = max2(ans[L][M], ans[L-movies[N].t][M-1]+movies[N].v);	
}

void output(int& M, int& L)
{
	if (ans[L][M] < 0)
		cout << 0 << endl;
	else
		cout << ans[L][M] << endl;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章