昨天试着做了一下腾讯马拉松程序设计大赛的题目,其中的第四题题目描述如下:
对于吃货来说,过年最幸福的事就是吃了,没有之一!
但是对于女生来说,卡路里(热量)是天敌啊!
资深美女湫湫深谙“胖来如山倒,胖去如抽丝”的道理,所以她希望你能帮忙制定一个食谱,能使她吃得开心的同时,不会制造太多的天敌。
当然,为了方便你制作食谱,湫湫给了你每日食物清单,上面描述了当天她想吃的每种食物能带给她的幸福程度,以及会增加的卡路里量。
Input
输入包含多组测试用例。
每组数据以一个整数n开始,表示每天的食物清单有n种食物。
接下来n行,每行两个整数a和b,其中a表示这种食物可以带给湫湫的幸福值(数值越大,越幸福),b表示湫湫吃这种食物会吸收的卡路里量。
最后是一个整数m,表示湫湫一天吸收的卡路里不能超过m。
[Technical Specification]
1. 1 <= n <= 100
2. 0 <= a,b <= 100000
3. 1 <= m <= 100000
Output
对每份清单,输出一个整数,即满足卡路里吸收量的同时,湫湫可获得的最大幸福值。
Sample Input
3
3 3
7 7
9 9
10
5
1 1
5 3
10 3
6 8
7 5
6
Sample Output
10
20
解题思路:
一开始,我没有意识到这是一个完全背包问题。我的思路是:先从每份清单中剔除那些热量超过了最大热量m的食物,然后计算每种食物的单位热量幸福值k(k=幸福值÷热量),并按k值降序排列,先选k值最大的食物,如果还有热量剩余,就选k值次大的食物,依此循环,直到剩余的热量值为0。
我的代码是这样的:
#include <iostream>
#include <cstdlib>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 100;
//定义一个食物类
class val
{
public:
int happy;//幸福值
int heat;//热量
float unitHapHea;//单位热量的幸福值
val():happy(0),heat(0),unitHapHea(0.0){}//构造函数
};
//定义按降序排列的比较函数
bool greaterFun(const val& v1,const val& v2)
{
if(v1.unitHapHea == v2.unitHapHea)
return v1.heat < v2.heat;
else
return v1.unitHapHea > v2.unitHapHea;
}
int main()
{
int n;
int k = 0;
int result[100];//保存结果的数组
memset(result,0,sizeof(result));
while (true)
{
cin>>n;
if(n == 0)
break;
if(!(n >= 1 && n <= 100))
{
cout<<"输入的n无效,请重新输入!"<<endl;
continue;
}
val happyAndHeat[100];
int a,b;
for(int i = 0;i < n;i++)
{
cin>>a>>b;
if(!(a >= 0 && a <= 100000 && b >=0 && b <= 100000))
{
cout<<"输入的a或b值无效,请重新输入!"<<endl;
cin>>a>>b;
}
happyAndHeat[i].happy = a;
happyAndHeat[i].heat = b;
happyAndHeat[i].unitHapHea = (double)a/(double)b;
}
int m = 0;
cin>>m;
vector<val> v;
for(int i = 0;i < n;i++)
{
if(happyAndHeat[i].heat > m)//如果某种食物的热量值大于最大值m,则忽略
continue;
v.push_back(happyAndHeat[i]);
}
sort(v.begin(),v.end(),greaterFun);//按单位热量的幸福值降序排列
int sum = 0;//幸福值总和
int heatLeft = m;//剩余可用的热量值
//按单位热量幸福值从大到小的顺序循环选择食物,直到剩余可用的热量值为零
for (int i = 0;i < v.size();i++)
{
if(heatLeft == 0)
break;
if(heatLeft / v[i].heat == 0)
continue;
sum += heatLeft / v[i].heat * v[i].happy;
heatLeft -= heatLeft / v[i].heat * v[i].heat;
}
result[k++] = sum;
}
//输出结果
for(int i = 0;result[i] != 0;i++)
cout<<result[i]<<endl;
}
但是在用
3
A食物 3 3
B食物 7 7
C 食物 9 9
10
这个例子测试的时候出错了,输出结果是9而不是10,因为这三种食物的单位热量幸福值都是1,排列顺序是A B C,于是10/3=3,即选3份A食物,得到的幸福值是3×3=9。但是,这并不是最优的,因为这时的热量值是9,没达到最大值10,如果A和B各选一份,则得到的幸福值为10,是最右结果。另一个例子
3
A食物 3 3
B食物 8 8
C 食物 10 9
11
因为C的单位热量幸福值最大,所以选一份C食物,得到幸福值10,但是A和B各选一份能得到最大的幸福值11。
对于第二个测试用例
5
1 1
5 3
10 3
6 8
7 5
6
是可以输出正确结果的,最大幸福值为20,即选2份第三种食物。分析后发现,这个方法有时候能得到正确结果,有时候得不到。原因在于我优先选择单位热量幸福值最大的食物,但是可能导致有剩余热量未被完全利用,即热量值没有达到最大值m,所以可能导致得不到最大幸福值。
后来在网上看到别人的分析,再一想才发现这是一个完全背包问题,所以得出以下的正确解法:
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
int dp[100006];
int main()
{
int t;
while(~scanf("%d",&t))
{
memset(dp,0,sizeof(dp));
int i,j,aim;
int ka[105],val[105];
for(i = 0; i<t; i++)
scanf("%d%d",&val[i],&ka[i]);
scanf("%d",&aim);
for(i = 0; i<t; i++) //状态转移方程
{
for(j = ka[i]; j<=aim; j++)
{
dp[j] = max(dp[j],dp[j-ka[i]]+val[i]);
}
}
printf("%d\n",dp[aim]);
}
scanf("%d",&t);
return 0;
}
经过编程竞赛题目的练习,才发觉自己要学的东西还有很多。能够正确建立模型对于快速、准确解答题目是至关重要的,所以对于类似于背包问题这样的经典模型,要深刻理解才能更好地应用。虽然一开始没有想出最优的解法,但是探索的过程还是极大地锻炼了思维能力。以后多学习才能不断进步!