购物单(牛客)(01背包+分组背包+有依赖的背包)

题目描述:

王强今天很开心,公司发给N元的年终奖。王强决定把年终奖用于购物,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:

 

主件 附件
电脑 打印机,扫描仪
书柜 图书
书桌 台灯,文具
工作椅

 

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

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

v[j 1 ]*w[j 1 ]+v[j 2 ]*w[j 2 ]+ … +v[j k ]*w[j k ] 。(其中 * 为乘号)

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

输入描述:

输入的第 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 是所属主件的编号)

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

注意: 这个输入并不是乱序的,而是如下

 

主件0
主件0 的 附件1
主件0 的 附件2
主件1
主件2

//也就是说如果主件i有附件,那么一定是跟着输入进来的

 

输出描述:

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

2200

解题思路: 

见下 依赖的背包问题

注意:0-1背包 --> 分组背包 --> 有依赖的背包 是一个递进解决的思维过程

0-1背包(二维):

问题:共n个物体,第i个重量为w[i],价值v[i],背包最多能背不超过W的物体,求最大的价值

思路:

设置二维数组 dp[ i ][ j ];    i 代表第 i 个物体,j 代表当前容量,dp[ i ][ j ]代表的是前 i 个物体在容量为 j 请情况下所能得到的最大价值。

其中 i 的范围为 0~n, j 的范围为 0~W。初始化dp[ i ][ j ],当 i 为0,也就是说前0件物品,不管容量 j 多大,最大价值都为0,即dp[0][ j ]都为0;同理可得,dp[ i ][0]皆为0。

初试化完成后,我们要考虑的是,对于任意的dp[ i ][ j ]应该怎么求?

假设前 i-1 个物品的状态都已经更新完毕(即前 i-1 个物品在每一种容量下的最大价值都已经确定),对于第 i 个物品在容量为 j 是否可以加入到背包当中,有两种情况:

情形一:第 i 个物品不可以加入到背包中,那么dp[ i ][ j ] = dp[ i-1 ][ j ]; 因为容量不变,物品数没有增加,价值量就不变

情形二:第 i 个物品可以加入到背包当中,那么我们需要考虑的是,把它加进来价值高,还是不加进来价值高。取两者之间的最大值。同时加进来的话当前容量必须空出可以让这个物品加进来的容量。

因此0-1背包 二维解法递归核心:

if( j

0-1背包(一维):

一维的推导思路,是建立在二维的基础之上(参考:降维思想)

思路:

(减行可得:dp[n][W] → dp[2][W] )
第i个物体的更新,只依赖于第i-1个的物体的结果,所以我们只用维系一个行数为2的数组
所以可以用滚动数组,每次只存i和i-1个物体时候的每个j所对应的value值 
(删行可得:dp[2][W] → dp[W] )
第i个物体在容积为j状态的更新,只依赖i-1物体容量里j-w[i]的状态的结果
所以,对dp[i-1][j]从后面开始向前更新,则求j位置时候的值,j-w[i]的值依旧为i-1时候的值
相当于从后往前j--对 dp[ i-1 ][ j ]完成了一次重写覆盖,覆盖之后,这个一维数组就变成了dp[ i ][ j ]

一维0-1背包的核心:

for(int i=1; i=w[i]; j--){
	dp[j] = max(dp[j], dp[j-w[i]]+value[i]);
    }
}

也就是说随着外层循环 i 的变化,dp[ j ]代表着前 i 件物品在每一个容量 j 下所能取得的最大价值 

分组背包:

有N件物品,告诉你这N件物品的重量以及价值,将这些物品划分为K组,每组中的物品互相冲突,最多选一件,求解将哪些物品装入背包可使这些物品的费用综合不超过背包的容量,且价值总和最大。

首先0-1背包问题是一种特殊的分组背包问题,n个物品就是n个组

分组背包的核心思想:

for 所有的组k{
    for j=0..W{  //每组中选定方式的不同,会使对容量的需求不同,因此容量遍历从W到0
	for 所有的i属于组k{   //第k组中有多个不同的物品 第k组不同的i代表当第k组不同的物品
	    dp[j]=max{dp[j],dp[j-w[i]]+v[i]}; //意味着当前容量j下,所能得到的最大价值,来源于上一层的某一种选择(上一层总共有i+1种选择),这点保证了每一组都只会选择一种物品或者是0种物品
	}		
    }
}

注意: 

核心思想中的

for 所有的i属于组k{ 
    dp[j]=max{dp[j],dp[j-w[i]]+v[i]};;
}

一般是转换为多个 顺序的if语句进行处理

具体情况见AC代码

之所以,我先写核心思想,而不是思路。因为我是在0-1背包的一维基础上观察这个核心伪代码。两者对比,可以发现一个问题,都是在对每一组进行覆盖重写。不同的是,0-1背包,每一组只有一件物品,也就是两种选择。而分组背包,每一组的物品数量不缺定,假设当前组有m件物品,则有m+1种选择。max的核心含义在于挑出一个可以使的前k组在容量 j 下可达到的最大价值。

 

依赖的背包问题:

本题购物单,就是一个典型的依赖的背包问题。我们按照每个主件一定有两个配件来处理,如果输入只给了一个配件,那么另一个配件的权重和花费都为0。这个时候我们对这个主件和配件的选择构成了一组中的不同物品:

1. 仅主件

2.主件+配件1

3.主件+配件2

4.主件+配件1+配件2

也就是说,对于输入的数据,有依赖关系的主件和配件,我们要把它处理成前4种形式当做一组,代表一组四种可选择的物品,在此基础上我们也可以什么都不选。所以每组是五种选择方式

自此,得到 k 组物品,按照分组背包问题解决即可

AC代码:(这个对齐,加了注释之后就失灵了,无奈了)

#include
using namespace std;

int getMax(int x, int y){
    return (x > y ? x : y);
}

int main()
{
	int W; //代表所允许的总消费,即最大容量
	int m; //代表总共有N个组件
	
	while(cin>>W>>m){
		int weight[100][3]={0}; //记录每个主件及其附件的金额(容量)
		int value[100][3]={0}; //记录每个主件及其附件的价值(价值=金额*权重) 
		 
		for(int i=1; i<=m; i++){  //对m个组件进行数据处理 
			int v; //代表当前组件的金额(容量)
			int p; //代表当前组件的权重 
			int q; //代表当前组件是主件还是附件,如果为附件,q代表其对应的主件的编号(注意主件编号并不是连续的) 
			
			cin>>v>>p>>q; 
			
			v/=10; //缩小单个配件的价格(容量) 
			
			if( q==0 ){
				weight[i][0] = v;
				value[i][0] = v*p;
			}else{
				if( weight[q][1]==0 ){
					weight[q][1] = v;
					value[q][1] = v*p;
				}else{
					weight[q][2] = v;
					value[q][2] = v*p;
				}
			} //这样处理,weight[i:0~m-1] 中一部分组储存的是主件和对应配件的值,这些组的索引值正是主件的编号。其他组索引值下的内容是全0。 
		}
		
		int dp[3200]={0}; //dp总容量为3200 
		W/=10; //缩小最大容量的范围,因为金额是10的倍数 
		for(int i=1; i<=m; i++){ //对m组数据进行处理,其中部分组是无效数据,即全0,不影响对背包存放 
			if( weight[i][0]==0 ){ //跳过无效组 
				continue;
			} 
	    	for(int j=W; j>0; j--){
	    		//dp[j]的值 就是 不选择任何一种物品的情况 
	            if(j>=weight[i][0])       //只容纳主件对 dp[j]进行更新 
	                dp[j]=getMax(dp[j],dp[j-weight[i][0]]+value[i][0]);
	            if(j>=weight[i][0]+weight[i][1]) //在已经考虑只容纳主件的基础上,容纳主件和附件1对dp进行更新 
	                dp[j]=getMax(dp[j],dp[j-weight[i][0]-weight[i][1]]+value[i][0]+value[i][1]);
	            if(j>=weight[i][0]+weight[i][2]) //在之前的基础上,容纳主件和附件2对dp进行更新
	                dp[j]=getMax(dp[j],dp[j-weight[i][0]-weight[i][2]]+value[i][0]+value[i][2]);
	            if(j>=weight[i][0]+weight[i][1]+weight[i][2]) //在之前的基础上,容纳主件、附件1、附件2对dp进行更新 
	                dp[j]=getMax(dp[j],dp[j-weight[i][0]-weight[i][1]-weight[i][2]]+value[i][0]+value[i][1]+value[i][2]);
	        }
		} 
		cout<

 

你可能感兴趣的:(牛客网编程)