分组背包问题
至少拿一件物品的 0 1 背包问题
设 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示前 i i i 组,在总花费为 j j j 的情况下,最大价值。
对于边界:
d
p
[
0
]
[
j
]
dp[0][j]
dp[0][j] 表示前
0
0
0 组 的最大价值,因为不可能状物品,果断初始化为
0
0
0 。
d p [ i ] [ 0 ] dp[i][0] dp[i][0] 表示总花费为 0 0 0 的时候,前 i i i 组的最大价值。由于本题的物品花费可能为 0 0 0 ,所以在总花费为 0 0 0 的情况下,仍然有可能装入物品,故初态待定。而由于最大价值,所以初始化为 负无穷。
除了上述两种边界情况,由于求的是至少一件物品,所以一定会有物品加入到 d p dp dp数组当中,那么对于待定状态,我们是不知道初值是多少的(因为不可能不装物品,所以初始化不能是 0 0 0 )。再由于求的是最大,所以初始化为 负无穷。
该问题由于是每组至少取一个物品,而物品不能重复取。这相当于是在每组中,做了一次 0 1 背包问题,而最大价值跟上一组的状态有关。
那么对于 dp[i][j]有三个转移状态:
1、由于每组至少取一件,所以我可以是该组第一次取物品,那么就与上一组的状态有关。
所以有:
d
p
[
i
−
1
]
[
j
−
w
[
i
]
]
+
v
[
i
]
。
dp[i-1][j-w[i]]+v[i]。
dp[i−1][j−w[i]]+v[i]。
2、如果这是该组取得第2.3.4…件,即不是第一件的话,那么状态由“该组取的第一件”转化来。
所以有:
d
p
[
i
]
[
j
−
w
[
i
]
]
+
v
[
i
]
。
dp[i][j-w[i]]+v[i]。
dp[i][j−w[i]]+v[i]。
3、在该组中,不取这件物品,那么由于我一定要在本组中至少取一件,则状态由本组转移而来。
所以有:
d
p
[
i
]
[
j
]
。
dp[i][j]。
dp[i][j]。
每次 0 1 背包,取上面三种情况最大值即可。
在注意一下顺序:如果 V 先在外层,物品 内层,则对应的是 求每组至多一件 的情况。
而本题求的是至少一件,相当于在每组中做了一次二重循环的 0 1 背包问题,故需要先循环 物品,再枚举 体积。
代码如下:
#include<iostream>
#include<algorithm>
#include<string.h>
#include<vector>
using namespace std;
int N, M, K, s, p;
int dp[18][10008];
struct Good
{
int w;
int val;
}A[108];
vector<int> q[18];
int main()
{
while (~scanf("%d%d%d", &N, &M, &K))
{
for (int i = 1; i <= p; i++) {
q[i].clear();
}
p = 0;
for (int i = 1; i <= N; i++) {
scanf("%d%d%d", &s, &A[i].w, &A[i].val);
if (!q[s].size())
p++;
q[s].push_back(i);
}
if (p < K)
printf("Impossible\n");
else {
memset(dp, -1, sizeof(dp));
dp[0][0] = 0;
for (int i = 0; i <= M; i++) {
dp[0][i] = 0;
}
for (int i = 1; i <= K; i++) {
for (int j = 0; j < q[i].size(); j++) {
int temp = q[i][j];
for (int k = M; k >= A[temp].w; k--) {
dp[i][k] = max(dp[i ][k - A[temp].w] + A[temp].val, max(dp[i][k], dp[i-1][k - A[temp].w] + A[temp].val));
}
}
}
if (dp[K][M] == -1)
printf("Impossible\n");
else
printf("%d\n", dp[K][M]);
}
}
}