動態規划(1)——0-1背包問題
1 題目描述
對於一組不同重量、不可分割的物品,我們需要選擇一些裝入背包,在滿足背包最大重量限制的前提下,背包中物品總重量的最大值是多少呢?
2 輸入
第一行是物品的個數n(1≤n≤100000),背包容量w(1≤w≤1000000);
第二行是n個物品的重量。
3 輸出
輸出最大值
4 樣例輸入
5 9
2 2 4 6 3
5 樣例輸出
9
6 求解思路
把問題分解為多個階段,每個階段對應一個決策。我們記錄每一個階段可達的狀態集合(去掉重復的),然后通過當前階段的狀態集合,來推導下一個階段的狀態集合,動態地往前推進。這也是動態規划這個名字的由來,你可以自己體會一下,是不是還挺形象的?
7 C++版本代碼如下
#include <iostream>
#include <math.h>
#include <string.h>
using namespace std;
#define MAXNUM 100010
int dpFirst(int weight[], int n, int weightLimit){
bool states[n][weightLimit + 1];
memset(states, false, sizeof(states));
// 第一行單獨處理
states[0][0] = true;
if(weight[0] <= weightLimit)
states[0][weight[0]] = true;
// 動態規划狀態轉移
for(int i = 1; i < n; i++){
for(int j = 0; j < weightLimit + 1; j++)
if(states[i - 1][j]){
// 不裝
states[i][j + 0] = true;
// 裝
states[i][j + weight[i]] = true;
}
}
// 找出最后一個狀態下的背包值
int maxPower = 0;
for(int i = 0; i <= weightLimit; i++)
if(states[n - 1][i])
maxPower = i;
return maxPower;
}
int main()
{
int weight[5] = {2,2,4,6,3};
cout<<dpFirst(weight, 5, 9)<<endl;;
return 0;
}
因為只需要判斷最后一個狀態下的背包容量,所以可以使用一個數組states[weightLimit]即可解決問題,代碼可優化空間復雜度如下:
其中注意上一個二維數組的方法中數組weight的訪問可能越界,所以修改為j <= weightLimit - weight[i + 1]
保證了數組weight不會越界,表示“當前狀態”能裝下的。
int dpFirst(int weight[], int n, int weightLimit){
bool states[weightLimit + 1];
memset(states, false, sizeof(states));
// 單獨處理第一個狀態
states[0] = true;
if(weight[0] <= weightLimit)
states[weight[0]] = true;
// 動態規划狀態轉移用一個數組解決
for(int i = 1; i < n; i++){
// 可以直接寫成weightLimit - weight[i],而不是weightLimit
// 這樣可以少幾個判斷,並且保證了數組weight不會越界
for(int j = 0; j <= weightLimit - weight[i]; j++){
if(states[j])
// 裝
states[j+ weight[i]] = true;
}
}
// 找出最后一個狀態下的背包值
for(int i = weightLimit; i >= 0; i--)
if(states[i])
return i;
}