九度oj-1025

题目描述:

现有一笔经费可以报销一定额度的发票。允许报销的发票类型包括买图书(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;
}