您的位置:首页 > 编程语言 > C语言/C++

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

2016-08-06 17:40 447 查看
【问题描述】  

  

  小沐同学确信所有问题都有个多项式时间算法,为了证明,他决定自己去当一次旅行商,在上路之前,小 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;
}

考后反思:对于看似复杂的题目,要沉着冷静,将复杂的题目转化为几个稍微简单一点的小问题,比如本题中的物品有三类,一开始可能会被不同种类的物品吓到,但只要将不同类的物品分开讨论,题就简单了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  C++
相关文章推荐