题目描述:
现有一笔经费可以报销一定额度的发票。允许报销的发票类型包括买图书(A类)、文具(B类)、差旅(C类),要求每张发票的总额不得超过1000元,每张发票上,单项物品的价值不得超过600元。现请你编写程序,在给出的一堆发票中找出可以报销的、不超过给定额度的最大报销额。
输入:
测试输入包含若干测试用例。每个测试用例的第1行包含两个正数 Q 和 N,其中 Q 是给定的报销额度,N(N<=30)是发票张数。随后是 N 行输入,每行的格式为:
m Type_1:price_1 Type_2:price_2 ... Type_m:price_m
其中正整数 m 是这张发票上所开物品的件数,Type_i 和 price_i 是第 i 项物品的种类和价值。物品种类用一个大写英文字母表示。当N为0时,全部输入结束,相应的结果不要输出。
输出:
对每个测试用例输出1行,即可以报销的最大数额,精确到小数点后2位。
样例输入:
200.00 3
2 A:23.50 B:100.00
1 C:650.00
3 A:59.99 A:120.00 X:10.00
1200.00 2
2 B:600.00 A:400.00
1 C:200.50
1200.50 3
2 B:600.00 A:400.00
1 C:200.50
1 A:100.00
100.00 0
样例输出:
123.50
1000.00
1200.50
代码
法一: 深度优先遍历,尤其注意递归出口。
/*
模型:已知某集合中的若干元素 及一上限Q,求集合中元素的和不超过Q的最大值。
*/
#include <stdio.h>
double sum,max; //sum-允许报销的总金额,max-实际报销的最大金额
int num,cnt; //num-支票总数目,cnt-有效支票数
double r[40]; //r[]保存各有效支票的面值
bool isValid(char c,double d)
{
if(c-'A'<3&&c-'A'>=0){
if(d<=600)
return true;
}
return false;
}
void dfs(int k,double tmpMax) //不包括当前下标k的最大值。r[]数组从0开始取到n-1.
{
if(tmpMax>sum) //递归出口1
return;
if(tmpMax>max)
max=tmpMax;
if(k>=cnt) //递归出口2
return;
dfs(k+1,tmpMax);
if(r[k]+tmpMax<=sum)//注意:此处 别漏了等号=。
dfs(k+1,tmpMax+r[k]);
}
/*
void dfs(int k,double tmpMax) //包括当前下标k在内的最大值。r[]数组从1开始取到n。
{
if(tmpMax>sum) //递归出口1
return;
if(k>cnt) //递归出口2
return;
if(tmpMax>max)
max=tmpMax;
if(r[k+1]+tmpMax<=sum)//注意:此处 别漏了等号=。 此句也可以以去掉,即不剪枝,循环的多一点而已,也正确。。
dfs(k+1,tmpMax+r[k+1]);
dfs(k+1,tmpMax);
}
*/
int main()
{
// freopen("G:\\in.txt","r",stdin);
int n,i,j;
char tmpc;
bool isOk;
double tmpd,tmpSum;
while(scanf("%lf%d",&sum,&num)!=EOF){
if(num==0) break;
cnt=0;max=0;
for(i=0;i<num;i++){
tmpSum=0;isOk=1;
scanf("%d",&n);
for(j=0;j<n;j++){
scanf(" %c%*c%lf",&tmpc,&tmpd); //此处输入格式控制:需要空格。
tmpSum+=tmpd; //保存每行数据的累加和
if(isValid(tmpc,tmpd)==0) //一组数据无效则整行数据无效
isOk=0;
}
if(isOk==1){ //无效则不处理。
if(tmpSum<=1000)
r[cnt++]=tmpSum;
}
}
dfs(0,0);//从0下表开始,最大值存在max中。
printf("%.2lf\n",max);
}
return 0;
}
法2:动态规划。不要像书上那样弄二维,太浪费空间。。其实只需要一维的即可。
/*
模型:已知某集合中的若干元素 及一上限Q,求集合中元素的和不超过Q的最大值。
*/
#include <stdio.h>
double sum,max; //sum-允许报销的总金额,max-实际报销的最大金额
int num,cnt; //num-支票总数目,cnt-有效支票数
double r[40]; //r[]保存各有效支票的面值
int dp[3000100];//dp[i][j]表示j的容量用i件物品来装。开辟的二维数组太大会死机。。
bool isValid(char c,double d)
{
if(c-'A'<3&&c-'A'>=0){
if(d<=600)
return true;
}
return false;
}
int rmax(int a,int b)
{
return a>b?a:b;
}
int main()
{
int n,i,j;
char tmpc;
bool isOk;
double tmpd,tmpSum;
while(scanf("%lf%d",&sum,&num)!=EOF){
if(num==0) break;
cnt=0;max=0;
for(i=0;i<num;i++){
tmpSum=0;isOk=1;
scanf("%d",&n);
for(j=0;j<n;j++){
scanf(" %c%*c%lf",&tmpc,&tmpd); //此处输入格式控制:需要空格。
tmpSum+=tmpd; //保存每行数据的累加和
if(isValid(tmpc,tmpd)==0) //一组数据无效则整行数据无效
isOk=0;
}
if(isOk==1){ //无效则不处理。
if(tmpSum<=1000)
r[++cnt]=tmpSum;
}
}
int ans[35];
for(i=1;i<=cnt;i++)
ans[i]=(int)(r[i]*100);
int sumT=(int)(sum*100);//后面要加括号,不然sum就会先转换成整数,然后才*100.!!!!
for(i=0;i<=sumT;i++)
dp[i]=0;
for(i=1;i<=cnt;i++){
for(j=sumT;j>=ans[i];j--){ //sumT的容量放cnt件物品:小标从1->cnt可以取到;
dp[j]=rmax(dp[j],dp[j-ans[i]]+ans[i]);
}
}
printf("%.2lf\n",dp[sumT]/100.00);
}
return 0;
}