新背包问题(多重背包问题变形)——南昌理工学院ACM暑假集训队


一、新背包问题的由来

顾名思义呢,新背包问题就是简单背包(01背包)的升级升级升级版,当然了也是多重背包问题的升级版!!!!!01背包求的是最大装满下达到的最大价值。多重背包问题,每一种商品限购,而新背包呢,每一个商品都限购外,有多次询问样例,每一次询问有一个商品i不能购买,另外给出新的所携带总价钱让你去买东西。听起来是不是挺有意思的??那就先让我们来看看题目
点这里:[HEOI2013]EDEN的新背包问题
我是在牛客上看到的,因为最近一直在做背包,看到背包两个字就忍不住上啊!

二、解题

1.解题思路

题目简化:
有 n 个玩偶,每个玩偶有对应的价值、价钱,每个玩偶都可以被买有 限次,在携带的价钱 m 固定的情况下,如何选择买哪些玩偶以及每个玩偶买多 少个,才能使得选择的玩偶总价钱不超过 m,且价值和最大。
前半部分吧,做过多重背包就知道,这是一道经典题,而后半部分就是新背包问题的精髓所在了!
首先,第一步,动态规划,确定用一维还是二维,在这里我们要选择二维。为什么呢?因为用二维可以保存循坏之前的状态,这道题,我们需要用两个二维数组分别记录前i个商品,和后i个商品在不超过j价格下的方案;那么我们设front[i][j]状态为前i个价格不超过j的集合,back[i][j]状态为后i个价格不超过j的集合,状态表示为最大值max。于是,求di的时候根据前di个和后di个相加并根据多重背包的做法就能得到答案。下面来看看代码怎么实现的。

2.代码实现

计算前i种的方案
第一重循环,遍历n个商品
第二重循环,因为根据ei的取值最大为1000;所以在这里我们就用1000来取最大!
第三重循环呢,就是多重背包了,因为每个商品限购!
状态表示:front[i][j] = max(front[i][j], front[i - 1][j - ka[i]] + kb[i]);

	for (int i = 1; i <= n; i++)
		for (int j = 1000; j >= 0; j--)
		{
			front[i][j] = front[i - 1][j];
			for (int k = 1; k <= c[i]; k++)
			{
				if (j >= k* a[i]) {
					front[i][j] = max(front[i][j], front[i - 1][j - k*a[i]] + k*b[i]);
				}
				else break;
			}
		}

计算后i种的方案
后i种与前i钟不同在于先从后往前遍历每一个商品,之后一样计算价格在1000以下的选法,对于每一个状态由后面一个状态更新的,所以在这里
front[i][j] = front[i + 1][j];
状态表示:back[i][j] = max(back[i][j], back[i + 1][j - ka[i]] + kb[i]);

	for (int i = n; i >= 1; i--)
		for (int j = 1000; j >= 0; j--)
		{
			back[i][j] = back[i + 1][j];
			for (int k = 1; k <= c[i]; k++)
			{
				if (j >= k*a[i]) {
					back[i][j] = max(back[i][j], back[i + 1][j - k*a[i]] + k*b[i]);
				}
				else break;
			}
		}

输出最大值

	while (t--)
	{
		int m, n;
		cin >> m >> n;
		m++;
		int res = 0;
		for (int i = 0; i <= n; i++)
			res = max(res,front[m - 1][i]+ back[m + 1][n-i]);
		cout << res << endl;
	}

这是一道多重背包的变形题,这道题的解题就在于用两个二维数组保存前一种和后一种方法来取代在不选第i种物品下的选法!

代码

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1010;
int a[N], b[N], c[N];
int front[N][N];
int back[N][N];
int n;
int main()
{
	cin >> n;
	for (int i = 1; i <= n; i++)
		cin >> a[i] >> b[i] >> c[i];
	for (int i = 1; i <= n; i++)
		for (int j = 1000; j >= 0; j--)
		{
			front[i][j] = front[i - 1][j];
			for (int k = 1; k <= c[i]; k++)
			{
				if (j >= k* a[i]) {
					front[i][j] = max(front[i][j], front[i - 1][j - k*a[i]] + k*b[i]);
				}
				else break;
			}
		}

	for (int i = n; i >= 1; i--)
		for (int j = 1000; j >= 0; j--)
		{
			back[i][j] = back[i + 1][j];
			for (int k = 1; k <= c[i]; k++)
			{
				if (j >= k*a[i]) {
					back[i][j] = max(back[i][j], back[i + 1][j - k*a[i]] + k*b[i]);
				}
				else break;
			}
		}

	int t;
	cin >> t;
	
	while (t--)
	{
		int m, n;
		cin >> m >> n;
		m++;
		int res = 0;
		for (int i = 0; i <= n; i++)
			res = max(res,front[m - 1][i]+ back[m + 1][n-i]);
		cout << res << endl;
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值