HDU 1864 最大报销额(01背包)
题目网址:http://acm.hdu.edu.cn/showproblem.php?pid=1864
题目:
最大报销额
Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 25248 Accepted Submission(s): 7771
Problem Description
现有一笔经费可以报销一定额度的发票。允许报销的发票类型包括买图书(A类)、文具(B类)、差旅(C类),要求每张发票的总额不得超过1000元,每张发票上,单项物品的价值不得超过600元。现请你编写程序,在给出的一堆发票中找出可以报销的、不超过给定额度的最大报销额。
Input
测试输入包含若干测试用例。每个测试用例的第1行包含两个正数 Q 和 N,其中 Q 是给定的报销额度,N(<=30)是发票张数。随后是 N 行输入,每行的格式为:
m Type_1:price_1 Type_2:price_2 ... Type_m:price_m
其中正整数 m 是这张发票上所开物品的件数,Type_i 和 price_i 是第 i 项物品的种类和价值。物品种类用一个大写英文字母表示。当N为0时,全部输入结束,相应的结果不要输出。
m Type_1:price_1 Type_2:price_2 ... Type_m:price_m
其中正整数 m 是这张发票上所开物品的件数,Type_i 和 price_i 是第 i 项物品的种类和价值。物品种类用一个大写英文字母表示。当N为0时,全部输入结束,相应的结果不要输出。
Output
对每个测试用例输出1行,即可以报销的最大数额,精确到小数点后2位。
Sample Input
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
Sample Output
123.50
1000.00
1200.50
思路:
报销金额只精确到小数点后第二位,所以可以通过将报销金额*100转换成整型来操作。即将报销金额作为状态表示。dp[i]表示的是报销i金额所需的最少发票数。
状态转移:dp[j]=min(dp[j],dp[j-v[i]]+1);
代码:
1 #include <cstdio> 2 #include <vector> 3 #include <algorithm> 4 using namespace std; 5 const int INF=111111; 6 vector<int>v; 7 int dp[3000005]; 8 int p,n,m; 9 void input(){ 10 char ch; 11 double x; 12 for (int i=0; i<n; i++) { 13 int A=0,B=0,C=0; 14 scanf("%d",&m); 15 int flag=0; 16 for (int j=0; j<m; j++) { 17 scanf(" %c:%lf",&ch,&x); 18 switch (ch) { 19 case 'A': 20 A+=(int)(x*100); 21 break; 22 case 'B': 23 B+=(int)(x*100); 24 break; 25 case 'C': 26 C+=(int)(x*100); 27 break; 28 default: 29 flag=1; 30 break; 31 } 32 } 33 if(A<=60000 && B<=60000 && C<=60000 && (A+B+C)<=100000 && !flag) v.push_back(A+B+C); 34 } 35 } 36 int main(){ 37 double x; 38 while (scanf("%lf%d",&x,&n)!=EOF && n) { 39 p=(int)(x*100); 40 v.clear(); 41 input(); 42 for(int i=1; i<=p; i++) 43 dp[i]=INF;//初始化为无穷多张 44 for(int i=0; i<v.size(); i++){ 45 for (int j=p; j>=v[i]; j--) { 46 dp[j]=min(dp[j], dp[j-v[i]]+1);//求报销j金额所需的最小发票张数 47 } 48 } 49 for(int i=p;i>=0;i--){ 50 if(dp[i]!=INF){//dp[i]=INF则表示无法报销该金额数 51 printf("%.2lf\n",i/100.0); 52 break; 53 } 54 } 55 56 } 57 return 0; 58 }
另外我看到有人用报销发票的张数来表示转态的,自己也试着写了写,发现HDU上可以过。估计是测试样例太水,自己想了个例子就不对。
比如这组测试数据。他给的答案是6,而实际上可以报销7。这种写法漏了后面的几种组合。
7 4
1 A:1
1 A:3
1 A:2
1 A:4
错误代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <vector> 4 #include <algorithm> 5 using namespace std; 6 double q; 7 int n,m; 8 double dp[35]; 9 vector<double>v; 10 void input(){ 11 for (int i=0; i<n; i++) { 12 scanf("%d",&m); 13 int flag=0; 14 char ch; 15 double x,A=0,B=0,C=0; 16 for (int j=0; j<m; j++) { 17 scanf(" %c:%lf",&ch,&x); 18 switch (ch) { 19 case 'A': 20 A+=x; 21 break; 22 case 'B': 23 B+=x; 24 break; 25 case 'C': 26 C+=x; 27 break; 28 default: 29 flag=1; 30 break; 31 } 32 33 } 34 if(A<=600 && B<=600 && C<=600 && (A+B+C)<=1000 && !flag) v.push_back(A+B+C); 35 } 36 } 37 int main(){ 38 while (scanf("%lf%d",&q,&n)!=EOF && n) { 39 double res=0; 40 v.clear(); 41 memset(dp, 0, sizeof(dp)); 42 input(); 43 dp[0]=v[0]; 44 for (int i=0; i<v.size(); i++) { 45 for (int j=i-1; j>=0; j--) { 46 if(dp[j]+v[i]>q) continue; 47 dp[i]=max(dp[i], dp[j]+v[i]); 48 } 49 } 50 for(int i=0;i<v.size();i++) res=max(res, dp[i]); 51 printf("%.2lf\n",res); 52 } 53 return 0; 54 }