HDU #2191 買米問題 多重背包及其優化
Description
問題描述以及測試樣例在這:HDU#2191
思路
這道題其實就是多重背包問題,即有 N 種物品和一個容量為 V 的背包,第 i 種物品最多有 n[i] 件可用,每件費用是 c[i] ,價值是 w[i] ,求哪些物品裝入背包可以使得這些物品的費用總和不超過背包容量,且價值總和最大。在這道題中,背包容量是經費,費用是每種米的價格,價值是每種米的重量,求用給定的經費買米,使得買到米的總重量最大,註意經費是可以剩余的。
其實多重背包問題和完全背包問題非常相似,只不過前者的物品是有限件而後者物品是無限件。我們可以把完全背包的狀態轉移方程改造一下而得到多重背包的狀態轉移方程,如下:
dp[i][j] = max { dp[i-1][v-k*c[i]] + k*w[i] | 0 <= k <= n[i] }
算法通過窮舉 k 而得到所有的解,然後在從其中調出最大的。我們根據它很容易就可以寫出算法,時間復雜度為 O( V·∑n[i] ) :
#include<iostream> #include<algorithm> using namespace std; const int MAX_N = 100; //經費價格上限 const int MAX_M = 100; //大米種類上限 int price[MAX_M+1]; //價格 int weight[MAX_M+1View Code]; //重量 int num[MAX_M+1]; //袋數 int dp[MAX_M+1][MAX_N+1] = {0}; //最大重量 int main(void) { int test_num; cin >> test_num; while (test_num--) { int n, m; //經費金額, 大米種類 cin >> n >> m; for (int i = 1; i <= m; ++i) { cin >> price[i] >> weight[i] >> num[i]; }//用j經費買前i種米,能買到的最大總重量 for (int i = 1; i <= m; i++){ for (int j = 1; j <= n; j++) { int max_weight = 0; for (int k = 0; k <= num[i]; k++) { if (j-k*price[i] >= 0) { if (max_weight < dp[i-1][j-k*price[i]] + k*weight[i] ) { max_weight = dp[i-1][j-k*price[i]] + k*weight[i]; } } } dp[i][j] = max_weight; } } cout << dp[m][n] << endl; } return 0; }
然而,它和自頂向下的遞歸算法一比,時間和空間並沒有得到優化,所以我先從空間上去優化這個算法。
由於01背包問題和完全背包問題都有一維數組實現的算法,那麽我們可以試著把多重背包問題轉化成其中一個問題再進行求解。我先把它轉化成好寫且好理解的01背包問題。在01背包問題中需要我們以“件”為單位而不是以“種”為單位去看待物品,所以需要把一種物品拆分成多件物品。
如何拆分呢?
在多重背包裏,直接把第 i 種物品拆分成 n[i] 件就好啦。
額外再說個完全背包問題中拆分物品的方法。如果物品的個數是 N ,背包的容量是 V ,第 i 種物品的費用是 cost[i] ,那麽應該把第 i 種物品拆分成 V/cost[i] 件。舉個例子,如果物品個數是 3 ,背包的容量是 5 ,那麽拆分前的物品序列是下面圖左,拆分後的物品序列是下面的圖右:
拆分好之後,就直接套用一維滾動數組解決01背包問題的算法即可,空間復雜度優化到為 O(V) :
#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int MAX_N = 100; //經費價格上限 const int MAX_M = 100; //大米種類上限 int price[MAX_M+1]; //價格 int weight[MAX_M+1]; //重量 int num[MAX_M+1]; //袋數 int dp[MAX_N+1]; //最大重量 int main(void) { int test_num; cin >> test_num; while (test_num--) { int n, m; //經費金額, 大米種類 cin >> n >> m; for (int i = 1; i <= m; ++i) { cin >> price[i] >> weight[i] >> num[i]; } //初始化dp memset(dp, 0, sizeof(dp)); //用j經費買前i種米,能買到的最大總重量 for (int i = 1; i <= m; i++){ //對第i種米進行num[i]次01選擇 for (int k = 1; k <= num[i]; k++) { for (int j = n; j >= price[i]; j--) { //要麽選,要麽不選 dp[j] = std::max(dp[j], dp[j-price[i]] + weight[i] ); } } } /* cout << "檢查中" << endl; for (int i = 0; i <= m; i++) { for (int j = 0; j <= n; j++){ cout << dp[i][j] << " "; } cout << endl; } */ cout << dp[n] << endl; } return 0; }View Code
如何優化時間復雜度呢?這個我暫時沒有思路,之後若有了話再繼續寫博。
HDU #2191 買米問題 多重背包及其優化