多重背包(HDU2191)

今天看了下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;
}
以上有错误的地方希望大家指出^ ^
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值