多重背包问题描述:有编号分别为a,b,c的三件物品,它们的重量分别是1,2,2,它们的价值分别是6,10,20,他们的数目分别是10,5,2,现在给你个承重为 8 的背包,如何让背包里装入的物品具有最大的价值总和?
多重背包和01背包、完全背包的区别:多重背包中每个物品的个数都是给定的,可能不是一个,绝对不是无限个。
有两种解法,解题思路:
- 作为一个新问题考虑,由于每个物品多了数目限制,因此初始化和递推公式都需要更改一下。初始化时,只考虑一件物品a时,f[1][j] = min{num[1], j/weight[1]}。 计算考虑i件物品承重限制为y时最大价值f[i][y]时,递推公式考虑两种情况,要么第 i 件物品一件也不放,就是f[i-1][y], 要么第 i 件物品放 k 件,其中 1 <= k <= (y/weight[i]),考虑这一共 k+1 种情况取其中的最大价值即为f[i][y]的值,即f[i][y] = max{f[i-1][y], (f[i-1][y-k*weight[i]]+k*value[i])}。 这里为什么不能像完全背包一样直接考虑f[i][y-weight[i]]+value[i]呢?因为这样不容易判断第 i 件物品的个数是否超过限制数量 num[i]。
name | weight | value | num | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
c | 2 | 20 | 2 | 0 | 6 | 20 | 26 | 40 | 46 | 52 | 58 | 64 |
b | 2 | 10 | 5 | 0 | 6 | 12 | 18 | 24 | 30 | 36 | 42 | 48 |
a | 1 | 6 | 10 | 0 | 6 | 12 | 18 | 24 | 30 | 36 | 42 | 48 |
代码如下:
#include <iostream>
using namespace std;
int knapsack_limitnum(int *W, int *V, int *N, int *res, int n, int C)
{
int value = 0;
int **f = new int*[n];
for(int i = 0; i < n; i++)
{
f[i] = new int[C+1];
}
for(int i = 0; i < n; i++)
for(int j = 0; j < C+1; j++)
f[i][j] = 0;
for(int y = 1; y < C+1; y++)
{
int count = min(N[0], y/W[0]);
f[0][y] = (y < W[0])?0:(count * V[0]);
}
for(int i = 1; i < n; i++)
{
for(int y = 1; y < C+1; y++)
{
if(y < W[i])
{
f[i][y] = f[i-1][y];
} else {
int count = min(N[i], y/W[i]);
f[i][y] = f[i-1][y];
for(int k = 1; k <= count; k++)
{
int temp = f[i-1][y-W[i]*k] + k*V[i];
if(temp >= f[i][y])
f[i][y] = temp;
}
}
}
}
for(int i = 0; i < n; i++)
{
for(int y = 0; y < C+1; y++)
cout << f[i][y] << " ";
cout << endl;
}
value = f[n-1][C];
int j = n-1;
int y = C;
while(j)
{
int count = min(N[j], y/W[j]);
for(int k = count; k > 0; k--)
{
if(f[j][y] == (f[j-1][y-W[j]*k]+k*V[j]))
{
res[j] = k;
y = y - k*W[j];
break;
}
}
j--;
}
res[0] = f[0][y]/V[0];
for(int i = 0;i < n; i++)
{
delete f[i];
f[i] = 0;
}
delete [] f;
f = 0;
return value;
}
void test1()
{
int n, C;
while(cin >> n >> C)
{
int *W = new int[n];
int *V = new int[n];
int *N = new int[n];
int *res = new int[n];
for(int i =0; i < n; i++)
res[i] = 0;
int w, v, n1, i = 0;
while(i < n)
{
cin >> w >> v >> n1;
W[i] = w;
V[i] = v;
N[i] = n1;
i++;
}
int value = knapsack_limitnum(W, V, N, res, n, C);
cout << value << endl;
for(int i = 0; i < n; i++)
cout << res[i] << " ";
cout << endl;
delete res; res = 0;
delete N; N = 0;
delete V; V = 0;
delete W; W = 0;
}
}
int main()
{
test1();
return 0;
}
输入示例:
3 8 //3件物品,背包承重最大为8
1 6 10 //第一件物品, 重量为1,价值为6, 数目为10
2 10 5
2 20 2
输出结果:
0 6 12 18 24 30 36 42 48 //放入第一种物品,承重限制为0-8的最优值结果
0 6 12 18 24 30 36 42 48 //放入第一种和第二种物品,承重限制为0-8的最优值结果
0 6 20 26 40 46 52 58 64 //放入三种物品,承重限制为0-8的最优值结果
64 //原问题的解,在背包承重为8中放3种物品最大价值为64
4 0 2 //最优解对应第一件物品放4个,第二件物品放0个,第三件物品放2个,即4*6+0*10+2*20 = 64
- 多重背包的第二种解法,由01背包的分析可知,01背包中允许放入的物品有重复,即01背包中如果考虑要放入的物品的重量和价格相同,不影响最终的结果,因为我们可以考虑把多重背包问题中限制数目的物品拆分成单独的一件件物品,作为01背包问题考虑。问题解法和01背包一致,这里不再列举出动态规划的表格了。
代码:
#include <iostream>
#include <vector>
using namespace std;
int knapsack_limitnum(vector<int> &W, vector<int> &V, vector<int> &res, int C)
{
int value = 0;
int **f = new int*[W.size()];
for(int i = 0; i < W.size(); i++)
{
f[i] = new int[C+1];
}
for(int i = 0; i < W.size(); i++)
{
f[i][0] = 0;
}
for(int i = 1; i < C+1; i++)
{
f[0][i] = (i < W[0])?0:V[0];
}
for(int i = 1; i < W.size(); i++)
{
for(int y = 1; y < C+1; y++)
{
if(y < W[i])
{
f[i][y] = f[i-1][y];
} else {
f[i][y] = (f[i-1][y] > (f[i-1][y-W[i]]+V[i]))?f[i-1][y]:(f[i-1][y-W[i]]+V[i]);
}
}
}
value = f[W.size()-1][C];
int j = W.size()-1;
int y = C;
while(j)
{
if(f[j][y] == (f[j-1][y-W[j]]+V[j]))
{
res[j] = 1;
y -= W[j];
}
j--;
}
res[0] = (f[0][y])?1:0;
for(int i = 0; i < W.size(); i++)
{
delete f[i];
f[i] = 0;
}
delete [] f;
f = 0;
return value;
}
void test1()
{
int n, C;
while(cin >> n >> C)
{
vector<int> W;
vector<int> V;
int *N = new int[n];
int w, v, n1, i = 0;
while(i < n)
{
cin >> w >> v >> n1;
N[i] = n1;
while(n1--)
{
W.push_back(w);
V.push_back(v);
}
i++;
}
vector<int> res;
for(int i = 0;i < W.size(); i++)
res.push_back(0);
int value = knapsack_limitnum(W, V, res, C);
cout << value << endl;
i = 0;
int j = 0;
int count = 0, sum = N[i];
while(j < res.size())
{
if(j < sum)
{
if(res[j])
count++;
} else {
cout << count << " ";
i++;
sum += N[i];
count = (res[j])?1:0;
}
j++;
}
cout << count << endl;
delete N; N = 0;
}
}
int main()
{
test1();
return 0;
}
输入示例:
3 8 //3件物品,背包承重最大为8
1 6 10 //第一件物品, 重量为1,价值为6, 数目为10
2 10 5
2 20 2
执行结果:
64 //原问题的解,在背包承重为8中放3种物品最大价值为64
4 0 2 //最优解对应第一件物品放4个,第二件物品放0个,第三件物品放2个,即4*6+0*10+2*20 = 64