洛谷动态规划训练(5):P1064 金明的预算方案 有附件的背包问题

P1064 金明的预算方案 有附件的背包问题
本题要点总结:

1.关于构造合适的结构来存储数据,比如将零件对应的vpq存在list结构中,然后对于主件对应的零件的信息存在一个二维的数组中。并且这个二维数组的大小是groupdivision[61][61],因为附件最多60个。

2.关于不同背包问题的循环顺序整理。

如果是普通的01背包,那么求解的问题是有一系列的物品,每个物品可以选择装还是不装,要求j元时的最大价值。此时循环为

//01背包
for i=1:m;//m件物品
   for j=N:0;//最多有N元
   dp[j]=max{dp[j],dp[j-list[i].v]+list[i].w};

如果是分组背包,那么要求解的是有k组物品,每个组内的物品只能选一样,要求j元的最大值

//分组背包,注意循环顺序
for k=1:K
  for  j=N:0;
    for i=1:m
    dp[j]=max{dp[j],dp[j-list[i].v]+list[i].w};
预处理

如果是有附件的背包,那么我们可以按照如下的思路来进行解决。首先我们容易知道,每一种不同的购买方案是相互独立的,从某种程度上来说,你可以把它当成是一个分组背包问题,这个组只有1个,j=N:0,然后可以选择的物品的数量就是所有可以选择的方案的数量,而每个物品的cost和价值就对应这个方案的cost和价值。那么我们还可以继续划分,比如说我们考虑对每一个组内的物品的分配方案进行划分,我们可以得到单纯只选择这个组内的物品,在花费恰好为j元时得到的最大价值,然后就可以得到k个这样的一维数组(有k个组)。对于每个j元,我们可能并不太清楚它具体是选了什么组件,但是我们能够知道它对应的j元的能够拿到的最大的价值(其实这就是一个01背包的处理)

对于这些一维数组,我们使用的是“恰好”背包(为了减少计算量)。另外我们需要将这些方案存储下来,我们对k个一维数组进行扫描,把每个一维数组中有意义的方案的cost和val存下来(相当于把他们当成一个物品)。比如可以用一个这样的结构V[i][j],P[i][j],它们的含义分别是第i组第j个方案的花销cost和第i组第j个方案的价值。

求解

准备好了这些方案之后我们就可以开始求解了,不过每一次递增的时候是按照一组一组来考虑的。这个求解的过程类似于分组背包,因此先循环钱,再循环对应的方案

for k=1:K;//有K组
	for j=N:0;//
		for i=1:m;//循环对应的方案
			dp[j]=max{dp[j],dp[j-V[k][i]]+P[k][i]};

特别的,我们这里不使用恰好背包,因此第一行全设为0,然后去遍历所有的钱求最大的利益值,这个值就是我们所求的结果。
如果使用恰好背包,那么除了dp[0]=0;其余全设为负无穷。

如果是第一种问法,要求恰好装满背包,那么在初始化时除了f[0]为0其它f[1…V]均设为-∞,这样就可以保证最终得到的f[N]是一种恰好装满背包的最优解。

如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将f[0…V]全部设为0。

为什么呢?可以这样理解:初始化的f数组事实上就是在没有任何物品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量为0的背包可能被价值为0的nothing“恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态,它们的值就都应该是-∞了。如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为0,所以初始时状态的值也就全部为0了。

题目描述

金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间。更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过NN元钱就行”。今天一早,金明就开始做预算了,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:

主件 附件

电脑 打印机,扫描仪

书柜 图书

书桌 台灯,文具

工作椅 无

如果要买归类为附件的物品,必须先买该附件所属的主件。每个主件可以有00个、11个或22个附件。附件不再有从属于自己的附件。金明想买的东西很多,肯定会超过妈妈限定的NN元。于是,他把每件物品规定了一个重要度,分为55等:用整数1-51−5表示,第55等最重要。他还从因特网上查到了每件物品的价格(都是1010元的整数倍)。他希望在不超过NN元(可以等于NN元)的前提下,使每件物品的价格与重要度的乘积的总和最大。

在这里插入图片描述

输入格式
第11行,为两个正整数,用一个空格隔开:

N mNm (其中N(<32000)N(<32000)表示总钱数,m(<60)m(<60)为希望购买物品的个数。) 从第22行到第m+1m+1行,第jj行给出了编号为j-1j−1的物品的基本数据,每行有33个非负整数

v p qvpq (其中vv表示该物品的价格(v<10000v<10000),p表示该物品的重要度(1-51−5),qq表示该物品是主件还是附件。如果q=0q=0,表示该物品为主件,如果q>0q>0,表示该物品为附件,qq是所属主件的编号)

输出格式
一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(<200000<200000)。

输入输出样例
输入 #1复制
1000 5
800 2 0
400 5 1
300 5 1
400 3 0
500 2 0
输出 #1复制
2200

代码

#include<iostream>
#include<algorithm>
#include<cstring>
#include<set>
#include<string>
#include<vector>
using namespace std;

struct obj {
	int v;
	int p;
	int q;
};

int main() {
	obj list[61];
	int N, m;
	cin >> N >> m;
	int cnt[61] = {};//统计每一组的件的数量
	int groups = 0;//统计有多少组
	obj GroupDivision[61][61];//建立一个分组的数组
	
	for (int i = 1; i <= m; i++) {
		cin >> list[i].v >> list[i].p >> list[i].q;
		groups = max(groups, list[i].q);
		if (list[i].q) {//如果是附件
			cnt[list[i].q]++;//对应的组的附件计数增加
			GroupDivision[list[i].q][cnt[list[i].q]].v = list[i].v;//填充信息
			GroupDivision[list[i].q][cnt[list[i].q]].p = list[i].p;
			GroupDivision[list[i].q][cnt[list[i].q]].q = list[i].q;
		}
	}

	/*思路:每一组内,所有的选择方案是互斥的,每一种方案可以看成是分组背包内的一个物品。
	假设该组内部有n1个物品,那么对于每个物品可以选可以不选,共有2^n1种方案,加上主件选不选共有2^n1+1种方案*/
	//对每一个组内的背包做一次01规划
	//int dp[32001];
	int dp[201];
	int cnt2[61] = {};
	int V[61][11] = {};
	int P[61][11] = {};
	for (int i = 1; i <= m; i++) {
		if (!list[i].q) {//如果是主件
			memset(dp, -1, sizeof(dp));
			dp[0] = 0;
			for (int k = 1; k <= cnt[i]; k++) {
				for (int j = N - list[i].v; j >= 0; j--) {
					if (j >= GroupDivision[i][k].v&&dp[j - GroupDivision[i][k].v] != -1)
						dp[j] = max(dp[j], dp[j - GroupDivision[i][k].v] + GroupDivision[i][k].v*GroupDivision[i][k].p);
				}
			}
			//01背包处理完之后,得到的dp[j]的含义是:对于第i组的物品,恰好花费j元的附件的钱,能够得到的最大的利益;每一个j对应一个方案,互相排斥
			//遍历完该主件的组之后,还需要对这个组内有效的方案进行记录,这个记录应该在主件内部才进行记录
			for (int j = 0; j <= N - list[i].v; j++) {
				if (dp[j] != -1) {
					cnt2[i]++;
					V[i][cnt2[i]] = j + list[i].v;
					P[i][cnt2[i]] = dp[j] + list[i].v*list[i].p;
				}
			}
		}
		//下面这个判断我觉得和j==0的判断是重复了,其实可以不写
		if (!list[i].q) {
			cnt2[i]++;
			V[i][cnt2[i]] = list[i].v;
			P[i][cnt2[i]] = list[i].v*list[i].p;
		}
	}
	memset(dp, 0, sizeof(dp));
	for (int i = 1; i <= m; i++) {
		for (int j = N; j >= 0; j--) {
			for (int k = 1; k <= cnt2[i]; k++) {
				if (j >= V[i][k])
					dp[j] = max(dp[j], dp[j - V[i][k]] + P[i][k]);
			}
		}
	}
	int res = 0;
	for (int j = 0; j <= N; j++) {
		res = max(res, dp[j]);
	}
	cout << res;

	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值