【动态规划】多重背包问题及其优化

一、多重背包问题:

有一个最大容量为\(v\)的背包,有一系列物品,每一个物品都有体积\(v_i\)、价值\(w_i\)和数量\(s\)三个属性,要求选出一部分物品(每个物品可以选的个数不超过其数量),在背包能装得下它们的前提下使其价值之和最大。

二、解决思路:

1.简单思路:

显然,多重背包问题中s件某个物品可以看成01背包问题中的s个相同物品,因此,只需要在输入的部分稍作更改,即可直接套用01背包问题的解决方式:

代码如下:

/*
输入:物品的数量 背包的容量
接下来分别输入每个物品的体积、价值和数量

输出:最大价值
*/
#include <iostream>
using namespace std;
const int N = 10010;

class Thing {
public:
    int v, w;
};

Thing thing[N];//从1开始,体积,价值
int n, v;
int f[N][101];

/*
f[i][j]表示从前i个物品中选出最大体积为j的组合
可以根据不选第i个物品(f[i-1][j])和选至少一个第i个物品划分([i][j-v]+w)
从中选最大
*/

void solution() {
    cin >> n >> v;
    int num = 0;
    for (int i = 1; i <= n; i++) {//输入物品属性及其个数
        num++; 
        cin >> thing[num].v >> thing[num].w;
        int numn; cin >> numn; numn--;
        while (numn--) {
            num++;
            thing[num] = thing[num - 1];
        }
    }

    for (int i = 1; i <= num; i++) {
        for (int j = 0; j <= v; j++) {
            if (j - thing[i].v >= 0)
                f[i][j] = max(f[i - 1][j], f[i - 1][j - thing[i].v] + thing[i].w);
            else f[i][j] = f[i - 1][j];
        }
    }
    cout << f[num][v];
}
int main() {
    int t = 1;
    //cin >> t;
    while (t--) {
        solution();
    }
}

除此之外,也可以将多重背包问题转为加上个数限制完全背包问题(进行优化前),原理相似,此处不再赘述。

这种方法易于理解,但是时间复杂度较高,当数据量较大时易超时。

2.二进制优化:

二进制优化基于一个重要规律:1到任何一个正整数\(x\)之间的所有数都可以由\(2^0\)、\(2^1\)、\(2^2\)、...、\(2^n\)(\(x/4\leq 2^n\leq x/2\))、\(x-(2^{n+1}-1)\)中的一项或几项相加得来

简单证明:\(2^0\)、\(2^1\)、\(2^2\)、...、\(2^n\)可以表示出\(1\sim 2^{n+1}-1\)中的任何一个数,然后将此区间的上下两端都加上\(x-(2^{n+1}-1)\),即可表示\(x-(2^{n+1}-2)\sim x\)的所有数,显然\(x-(2^{n+1}-2)\leq 2^{n+1}-1\),得证。

可能产生的疑问:为什么要拆成\(2^0\)、\(2^1\)、\(2^2\)、...、\(2^n\)(\(x/4\leq 2^n\leq x/2\))、\(x-2^{n+1}-1\)而不是拆成\(2^0\)、\(2^1\)、\(2^2\)、...、\(2^n\)(\(x/4\leq 2^n\leq x/2\))、\(2^{n+1}\)呢?解答:如果拆成后者,最大可表示的数将是\(2^{n+2}-1\)而(很可能)不是x,从而导致算法可能取了超过x个的某个物品。

因此,我们可以将原先的s件某个物品转化为体积与价值分别为原来物品\(2^0\)、\(2^1\)、\(2^2\)、...、\(2^n\)(\(s/4\leq 2^n\leq s/2\))、\(s-(2^{n+1}-1)\)倍的一系列物品,接下来直接使用01背包问题的方法即可求解。

代码如下:

/*
输入:物品的数量 背包的容量
接下来分别输入每个物品的体积、价值和数量

输出:最大价值
*/
#include <iostream>
using namespace std;
const int N = 100010;

class Thing {
public:
	int v, w;
};

Thing thing[N];//从1开始,体积,价值
int n, v;
int f[N];

void solution() {
	cin >> n >> v;
	int num = 0;
	for (int i = 1; i <= n; i++) {
		int vi, wi, ni, bei = 1;
		cin >> vi >> wi >> ni;
		while (ni>0) {
			num++;
			if (ni - bei >= 0) {
				ni -= bei;
				thing[num].v = bei * vi;
				thing[num].w = bei * wi;
				bei *= 2;
			}
			else {
				thing[num].v = ni * vi;
				thing[num].w = ni * wi;
				ni = 0;
			}
		}

	}
	for (int i = 1; i <= num; i++) {
		for (int j = v; j >= thing[i].v; j--) {
			f[j]=max(f[j], f[j - thing[i].v] + thing[i].w);
		}
	}
	cout << f[v];
}
int main() { 
	int t = 1;
	//cin >> t;
	while (t--) {
		solution();
	}
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值