动态规划解决0-1背包问题
题目详情
给定一个承重量为C的背包,n个重量分别为w1 ,w2 ,…,wn的物品,物品i放入背包能产生pi (>0)的价值(i=1,2,…,n)。 每个物品要么整个放入背包,要么不放。要求找出最大价值的装包方案。
输入格式
输入的第一行包含两个正整数n和C(1≤n≤20),第二行含n个正整数分别表示n个物品的重量,第三行含n个正整数分别表示n个物品放入背包能产生的价值。
输出格式
在一行内输出结果,包括最大价值装包方案的价值、具体装包方案,用空格隔开。具体装包方案是n个物品的一个子集,用长度为n的0、1串表示(1表示对应物品被选中,0表示没有被选中)。如果这样的0、1串不唯一,取字典序最大的那个串。
输入样例
4 9
2 3 4 5
3 4 5 7
输出样例
12 1110
(注:1110 和0011都是价值最大的装包方案,取字典序最大的结果即为1110)
使用动态规划法解答
n代表n个物品,sizeOfBag代表背包可以装的重量。
构建一个二维数组表格,n+1行,sizeOfBag列。
table(n + 1, vector<int>(sizeOfBag + 1))
用以上的样例来构建的二维数组表格为:
装0 | 装1 | 装2 | 装3 | 装4 | 装5 | 装6 | 装7 | 装8 | 装9 | |
---|---|---|---|---|---|---|---|---|---|---|
不装物品 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1号物品 | 0 | 0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
2号物品 | 0 | 0 | 3 | 4 | 4 | 7 | 7 | 7 | 7 | 7 |
3号物品 | 0 | 0 | 3 | 4 | 5 | 7 | 8 | 9 | 9 | 12 |
4号物品 | 0 | 0 | 3 | 4 | 5 | 7 | 8 | 10 | 11 | 12 |
for (int i = 0; i <= n; i++) {
table[i][0] = 0;
}
for (int j = 0; j <= sizeOfBag; j++) {
table[0][j] = 0;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= sizeOfBag; j++) {
table[i][j] = table[i - 1][j];
if (weight[i] <= j)
table[i][j] = max(table[i][j], table[i - 1][j - weight[i]] + value[i]);
}
}
当目前背包重量大于等于该物品的weight时,选择最大价值的填入表格。
构建完该二维数组后,明显发现table[n][sizeOfBag]的值即为最大价值。
接下来,使用回溯法来得到装入背包,得到该最大价值的方案。
装0 | 装1 | 装2 | 装3 | 装4 | 装5 | 装6 | 装7 | 装8 | 装9 | |
---|---|---|---|---|---|---|---|---|---|---|
不装物品 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1号物品 | 0 | 0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
2号物品 | 0 | 0 | 3 | 4 | 4 | 7 | 7 | 7 | 7 | 7 |
3号物品 | 0 | 0 | 3 | 4 | 5 | 7 | 8 | 9 | 9 | 12 |
4号物品 | 0 | 0 | 3 | 4 | 5 | 7 | 8 | 10 | 11 | 12 |
检测table[i-1][j]是否等于table[i][j]
当table[i-1][j]==table[i][j]
,说明第 i 个物品没有被放入背包,输出答案方案的字符串reAns+="0"
当table[i-1][j]!=table[i][j]
,说明第 i 个物品被放入了背包中,此时背包的质量要减去第 i 个物品的weight,即reAns+="1"
,且下一个回溯到table[i-1][j-weight[i]]
代码如下:
void getReAns(const vv& table, string& reAns, int i, int j) {
if (table[i][j] == table[i - 1][j]) { //本题求的是字典序最大的,所以从这条路走
reAns += "0";
if (i != 1) //算法来到第一行即结束
getReAns(table, reAns, i - 1, j);
return; //既然走这条路了又不是要求所有的,就直接结束
}
else { //if (table[i][j] == table[i - 1][j - weight[i]] + value[i]) {
reAns += "1";
if (i != 1)
getReAns(table, reAns, i - 1, j - weight[i]);
}
}
此时得到的字符串reAns是倒序,使用reverse函数可以直接在原字符串上改变顺序,且无返回值
reverse(reAns.begin(), reAns.end());
完整代码
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef vector<vector<int> > vv;
int n, sizeOfBag;
vector<int> weight, value;
void fillTable(vv& table) {
for (int i = 0; i <= n; i++) {
table[i][0] = 0;
}
for (int j = 0; j <= sizeOfBag; j++) {
table[0][j] = 0;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= sizeOfBag; j++) {
table[i][j] = table[i - 1][j];
if (weight[i] <= j)
table[i][j] = max(table[i][j], table[i - 1][j - weight[i]] + value[i]);
}
}
}
void getReAns(const vv& table, string& reAns, int i, int j) {
if (table[i][j] == table[i - 1][j]) { //本题求的是字典序最大的,所以从这条路走
reAns += "0";
if (i != 1) //算法来到第一行即结束
getReAns(table, reAns, i - 1, j);
return; //既然走这条路了又不是要求所有的,就直接结束
}
else { //if (table[i][j] == table[i - 1][j - weight[i]] + value[i]) {
reAns += "1";
if (i != 1)
getReAns(table, reAns, i - 1, j - weight[i]);
}
}
int main() {
cin >> n >> sizeOfBag;
int tmp;
weight.push_back(0);//填充0位
value.push_back(0);
for (int i = 0; i < n; i++) {
cin >> tmp;
weight.push_back(tmp);
}
for (int i = 0; i < n; i++) {
cin >> tmp;
value.push_back(tmp);
}
vv table(n + 1, vector<int>(sizeOfBag + 1));
fillTable(table);
string reAns = "";
getReAns(table, reAns, n, sizeOfBag);
reverse(reAns.begin(), reAns.end());
cout << table[n][sizeOfBag] << ' ' << reAns;
return 0;
}