背包问题-敲代码:依赖背包(01背包+分组背包)、金明的预算方案

1、P1064 金明的预算方案

题目描述

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

主件 附件

电脑 打印机,扫描仪

书柜 图书

书桌 台灯,文具

工作椅 无

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

设第jj件物品的价格为 v[ ​j],重要度为 w[ ​j],共选中了 k 件物品,编号依次为 j1​, j2​, … , jk​,则所求的总和为:

v[ ​j1​] × w[ ​j1​] + v[ ​j2​] × w[ ​j2​] + … + v[ ​jk​] × w[ ​jk​]。

请你帮助金明设计一个满足要求的购物单。

输入格式

第 1 行,为两个正整数,用一个空格隔开:

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

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

输出格式

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

输入#1

1000 5
800 2 0
400 5 1
300 5 1
400 3 0
500 2 0

输出#1

2200

题目链接:https://www.luogu.org/problem/P1064

2、题解-敲代码

(1)组内01背包,对附件进行01背包处理,每组生成多个新的 “ 组合物品 ”,包括(主件)、(主件+若干附件)。

(2)分组背包处理,从每组中至多选择一个 ” 组合物品 “。

/*
	输入:
		背包容量   物品数量 
		物品价格  重要度  依赖(0是主件,>0是附件,代表依赖于第几个物品(从1开始编号))
		物品价格  重要度  依赖
	 	物品价格  重要度  依赖

	输出:
		所选每件物品的价格与重要度的乘积的总和最大

	输入1:
		1000 5
		800 2 0
		400 5 1
		300 5 1
		400 3 0
		500 2 0
	输出1:
		2200
	
	输入2:
		10 5
		8 2 0
		4 5 1
		3 5 1
		4 3 0
		5 2 0
	 
	输出2:
		22
 */

#include <iostream>
#include <vector>
#include <map>
#include <climits>
using namespace std;

int main()
{
    int n;        //背包容量
    int m;        //物品数量,编号从1开始
    cin>>n>>m;
    
    vector<int> v(m+1);           //重量
    vector<int> p(m+1);           //收益 = 重量 * 重要度
    vector<int> q(m+1);           //依赖
    map<int, vector<int>> mp;     //主件,附件编号序列
    
    for(int i=1; i<=m; i++)
    {
	cin >> v[i] >> p[i] >> q[i];
	p[i] = p[i] * v[i];       //收益 = 重量 * 重要度
	
	//将附件加入主件的附件序列
	if(q[i]>0)
	    mp[q[i]].push_back(i);
    }
    
    //存放每组,“组合物品”
    vector<vector<int>> mpv;  //每个下标为一组,每组有效组合的,重量序列
    vector<vector<int>> mpp;  //每个下标为一组,每组有效组合的,收益序列
    
    //对所有主件,进行01处理,恰好
    for(int i=1; i<=m; i++)
    {
	//跳过附件
	if(q[i]>0) continue;
	
	//对除本主件之外的所有附件01处理
	int nn = n-v[i];   //最大容量,除去主件
	vector<int> dp(nn+1, INT_MIN);   //恰好初始化
	dp[0] = 0;
	for(auto k : mp[i])    //对每个附件
	{
	    for(int j=nn; j>=v[k]; j--)    //对不同容量
	    {
		dp[j] = max(dp[j], dp[j-v[k]] + p[k]);
	    }
	}
	
	//记录本组(主件i)的有效“组合物品”,(主件i)或者(主件i+若干附件)
	vector<int> tv;   //本组“组合物品”,重量序列
	vector<int> tp;   //本组“组合物品”,收益序列
	
	//将有效组合结果,加入有效组合数组
	for(int j=0; j<=nn; j++)
	{
	    if(dp[j]<0) continue;   //负无穷,无效,不能恰好
	    
	    tv.push_back(j + v[i]);       //记录有效组合的重量
	    tp.push_back(dp[j] + p[i]);   //记录有效组合的收益
	}
	
	//只考虑主件i,其实已经包含在上面。当j=0的时候,dp[j]=0。由于后续分组处理中,每组只选一个组合物品,因此,此处加不加都可以。
	//tv.push_back(v[i]);
	//tp.push_back(p[i]);
	
	
	//本组的“组合物品”重量、收益,插入
	mpv.push_back(tv);
	mpp.push_back(tp);
    }
    
    //分组处理,每个分组只取一个组合物品
    vector<int> dp2(n+1, 0);
    for(int k=0; k<(int)mpv.size(); k++)    //对于每个分组
    {
	for(int j=n; j>=0; j--)                  //容量
	{
	    for(int i=0; i<(int)mpv[k].size(); i++)   //组内成员(组内组合物品)
	    {
		int vv = mpv[k][i];  //新物品重量
		int pp = mpp[k][i];  //新物品收益
		
		if(j >= vv)
		    dp2[j] = max(dp2[j], dp2[j-vv] + pp); 
	    }
	}
    }
    cout << dp2[n] << endl;
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值