今天看了下HDU2191,0-1背包可写,但还是顺便学了一下多重背包= =
并且感觉每次写背包问题都会遇到各种思路或者代码上的错误,今天整理一下思路
0-1背包
有n个物品,每个物品有只有一个,每个物品i的体积为v[i],价值为w[i],背包容量为V,要求选择物品装入背包使最后背包的价值尽可能大
分析:
每个物品的状态就两种,选或者不选,因为只需要对n个物品扫一遍
定义状态:d[i][j]为将前i个物品装入容量为j的背包
转移方程:d[i][j] = max(d[i - 1][j], d[i - 1][j - v[i]] + w[i]); 分别代表不选第i个物品或者选择第i个物品
目标状态:d[n][V]
优化:
其实上面的代码可以对空间进行优化,因为对每个状态d[i][j],每个状态i只依赖于前一个状态i - 1, 于是对i进行循环的时候就可以写成d[j] = max(d[j], d[j - v[i]] + w[i]);
以及注意j要从后往前滚,因为d[i][j]依赖于d[i - 1][j - v[i]],不然会被覆盖
完全背包
有n个物品,每个物品有无限个,每个物品i的体积为v[i],价值为w[i],背包容量为V,要求选择物品装入背包使最后背包的价值尽可能大
分析:
不能简单的物品状态分为选或者不选了
可将这个问题抽象为DAG上的最长路
定义状态:d[j]为当前背包容量为j,背包的最大价值
转移方程:d[j] = max(d[j], d[j - v[i]] + w[i]);
此时从前往后滚表,因为d[0]-d[min]是预先知道的,因为min是最小的能满足的背包容量
多重背包
有n个物品,每个物品的数目为num[i],每个物品i的体积为v[i],价值为w[i],背包容量为V,要求选择物品装入背包使最后背包的价值尽可能大
分析:
对某个物品i,有num[i]个,比如物品2有4个,则可以看作:2,2,2,2,进而转化为0-1背包求解
也可以定义状态:d[i][j]为选前i个物品到容量为j的背包,则
转移方程:d[i][j] = max(d[i - 1][j], d[i - 1][j - kv[i]] + k * w[i]); 且有j >= kv[i] && 0 <= k <= num[i];
则需要对k从1到num[i]进行枚举,实际上同0-1背包
#####优化:
一个很好的思路是利用二进制 && 0-1背包的思想,将k拆分为1,2,4….作为该物品的系数,乘到物品的价值以及体积里面,比如11可以表示为:1,2,4,4(最后一个4是11 - 1 - 2 - 4),然后对其做0-1背包
结合HDU2191理解思路
#include <iostream>
#include <cstring>
#include <stack>
#include <vector>
#include <set>
#include <map>
#include <cmath>
#include <queue>
#include <sstream>
#include <iomanip>
#include <fstream>
#include <cstdio>
#include <cstdlib>
#include <climits>
#include <deque>
#include <bitset>
#include <algorithm>
using namespace std;
#define PI acos(-1.0)
#define LL long long
#define PII pair<int, int>
#define PLL pair<LL, LL>
#define mp make_pair
#define IN freopen("in.txt", "r", stdin)
#define OUT freopen("out.txt", "wb", stdout)
#define scan(x) scanf("%d", &x)
#define scan2(x, y) scanf("%d%d", &x, &y)
#define scan3(x, y, z) scanf("%d%d%d", &x, &y, &z)
#define sqr(x) (x) * (x)
#define pr(x) cout << #x << " = " << x << endl
#define lc o << 1
#define rc o << 1 | 1
#define pl() cout << endl
const int maxn = 105;
int d[maxn], n, mon;
//price, heavy, num;
int v[maxn], w[maxn], num[maxn];
void complete_pack(int price, int weight) {
for (int j = price; j <= mon; j++) {
d[j] = max(d[j], d[j - price] + weight);
}
}
void zero_one_pack(int price, int weight) {
for (int j = mon; j >= price; j--) {
d[j] = max(d[j], d[j - price] + weight);
}
}
int multiple_pack() {
memset(d, 0, sizeof(d));
for (int i = 1; i <= n; i++) {
//物品数目足够,即可看作物品无限,当成完全背包处理
if (v[i] * num[i] >= mon) complete_pack(v[i], w[i]);
else {
int k = 1;
while (k < num[i]) {
//将该物品做0-1背包
zero_one_pack(k * v[i], k * w[i]);
num[i] -= k;
k <<= 1;
}
zero_one_pack(num[i] * v[i], num[i] * w[i]);
}
}
return d[mon];
}
int main() {
//IN;
int T;
scan(T);
while (T--) {
scan2(mon, n);
for (int i = 1; i <= n; i++) scan3(v[i], w[i], num[i]);
cout << multiple_pack() << endl;
}
return 0;
}