复赛模拟试题 物品选取(重庆一中高2018级信息学竞赛测验7) 解题报告

【问题描述】  
  
  小沐同学确信所有问题都有个多项式时间算法,为了证明,他决定自己去当一次旅行商,在上路之前,小 X 需要挑选一些在路上使用的物品,但他只有一个能装体积为 m 的背包。显然,背包问题对小沐来说过于简单了,所以他希望你来帮他解决这个问题。


  小沐可以选择的物品有 n样,一共分为甲乙丙三类: 
  1.甲类物品的价值随着你分配给他的背包体积变化,它的价值与分配给它的体积满足函数关系式,v(x) = A*x^2-B*x,x表示分配给该物品的体积,为非负整数,A,B是每个甲类物品的两个参数。注意每个体积的甲类物品只有一个。 
  2.乙类物品的价值 A和体积 B都是固定的,但是每个乙类物品都有个参数C,表示这个物品可供选择的个数。
  3.丙类物品的价值 A和体积 B也是固定的,但是每个丙类物品可供选择的个数都是无限多个。


  你最终的任务是确定小沐的背包最多能装有多大的价值上路。 
 
    
 【输入格式】  
  
  第一行两个整数 n,m,表示背包物品的个数和背包的体积; 
  接下来 n行,每行描述一个物品的信息。第一个整数 x,表示物品的种类: 
  若 x 为1表示甲类物品,接下来两个整数 A,B,为A类物品的两个参数; 
  若 x 为2表示乙类物品,接下来三个整数 A,B,C。A表示物品的价值,B表示它的体积,C 表示它的个数; 
  若 x 为3表示丙类物品,接下来两个整数A,B。A表示它的价值,B表示它的体积。


 
    
 【输出格式】  
   
  仅一行为一个整数,表示小 X的背包能装的最大价值。
 
    
 【输入样例】   
   
【样例1】
 1 0
 1 1 1


【样例2】
 4 10
 2 1 2 1
 1 1 2
 3 5 2
 2 200 2 3


 
    
 【输出样例】  
   
【样例1】
 0


【样例2】
 610


 
    
 【数据范围】  
   
对于50%的数据,只有乙和丙两类物品; 
对于70%的数据,1<=n<=100, 1<=m<=500,0<=A,B,C<=200; 
对于100%的数据,1<=n<=100, 1<=m<=2000,0<=A,B,C<=200;

 

做题思路(正解):拿到这道题时,首先想到背包问题,但这道题有和普通的0/1背包问题有点区别,即物品分了三类,对此,可以在动态规划时分类进行讨论。既然算法仍然是0/1背包问题的动态规划,那首先就要设状态函数,设f(i,j)表示前i类物品选择出体积为j的最大价值,因为分析前i类物品只与前i-1类物品有关,所以可以使用一维滚动数组,将i省略,但相应的j就要逆向枚举。分析第i类物品时,如果它是甲类物品,那么它可以不选,可以选,且选的体积可以从0到j,所以此时的状态转移方程为f(j)=max(f(j),f(j-x)+aa[i].a*x*x-aa[i].b*x)(0<=x<=j);如果它是乙类物品,那么它可以不选,也可以选,最多只能选aa[i].c个,此时的状态转移方程为f(j)=max(f(j),f(j-k*aa[i].b)+k*aa[i].a)(0<k<=aa[i].c && aa[i].b*k<=j);如果它是丙类物品,同样它可以选,可以不选,虽然可以选无限个,但其实最多只能选j/aa[i].b个,此时的状态转移方程为 f(j)=max(f(j),f(j-k*aa[i].b)+k*aa[i].a)(0<k<=j/aa[i].b),不过在枚举该类物品时,有个技巧,因为用的是一维滚动数组,可以直接正向枚举j,就不用再枚举k,当然,也可以逆向枚举j,再循环枚举k。这种设法的边界条件为f(0)=0,无论选择多少物品,如果选出的体积为0,那么最大价值也为0,即不选物品。最后答案需要在d[j](0<=j<=m)中选择最大值。


#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
typedef long long LL;
const int maxn=105;
const long long inf=1000000000010ll;
int N,M;
LL ans=-inf;
struct data  //记录物品
{
	int a,b,c,x;
};
data aa[maxn];
/*
f(j)表示前i类物品选择出体积为j的最大价值
甲类物品: f(j)=max(f(j),f(j-x)+aa[i].a*x*x-aa[i].b*x)(0<=x<=j)
乙类物品:f(j)=max(f(j),f(j-k*aa[i].b)+k*aa[i].a)(0<k<=aa[i].c) 
丙类物品:f(j)=max(f(j),f(j-k*aa[i].b)+k*aa[i].a)(0<k<=j/aa[i].b)
边界:f(0)=0 
*/
long long d[2005];
void solve()  //动态规划
{
	for(int i=0;i<=M;i++)
	d[i]=-inf;  //初始化
	d[0]=0;  //边界
	for(int i=1;i<=N;i++)
	{
		if(aa[i].x==1)  //甲类物品
		{
			for(int j=M;j>=0;j--)
			for(int k=j;k>=0;k--)
			d[j]=max(d[j],d[j-k]+aa[i].a*k*k-aa[i].b*k);
		}
		if(aa[i].x==2)  //乙类物品
		{
			for(int j=M;j>=aa[i].b;j--)
			for(int k=0;k<=aa[i].c && k*aa[i].b<=j;k++)
			d[j]=max(d[j],d[j-k*aa[i].b]+k*aa[i].a);
		}
		if(aa[i].x==3)  //丙类物品
		{
			for(int j=aa[i].b;j<=M;j++)
			d[j]=max(d[j],d[j-aa[i].b]+aa[i].a);
		}
	}
	for(int i=0;i<=M;i++)  
	ans=max(ans,d[i]);
	cout<<ans<<'\n';
}
int main()
{
	freopen("select.in","r",stdin);
	//freopen("select.out","w",stdout);
	scanf("%d%d",&N,&M);
	int x;
	for(int i=1;i<=N;i++)
	{
		scanf("%d",&aa[i].x);
		if(aa[i].x==1)  scanf("%d%d",&aa[i].a,&aa[i].b);
		if(aa[i].x==2)  scanf("%d%d%d",&aa[i].a,&aa[i].b,&aa[i].c);
		if(aa[i].x==3)  scanf("%d%d",&aa[i].a,&aa[i].b);
	}
	solve();
	return 0;
}

考后反思:对于看似复杂的题目,要沉着冷静,将复杂的题目转化为几个稍微简单一点的小问题,比如本题中的物品有三类,一开始可能会被不同种类的物品吓到,但只要将不同类的物品分开讨论,题就简单了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值