一,概述
1. 问题描述
给定最大可报销额度以及一定数量的发票,要求在发票中找出在最大可报销额度内的最大的发票报销额,有限制如下:
1)每张发票中可能有属于不同消费类别的项目,仅包括某些类别的发票才可报销
2)单张发票总金额不超过1000才可报销
3)单张发票中单个类别的项目金额不超过600才可报销
2. 问题链接
3. 问题截图
1.1 问题截图
二,算法思路
这道题特殊在给定的最大可报销额是未知的,并且不是整数,否则就是通常的01背包问题,所以用于解决01背包问题的做法不可行(即先根据给定的最大报销额构建数组,然后迭代的完成数组的求值)。但是考虑到发票的个数十分小(最多只有30),同时状态转移方程还是相同的(即还是F[i, v] = max(F[i-1, v], F[i-1, v-p[i]])),只是不能用数组去表示这样的方程(因为数组没有小数下标),所以这道题可以使用自上而下的方法去解答,即用递归的方法求解。
同时考虑到自上而下的求解可能会导致同一个子问题被多次求解,在实现上可以采用某种策略保存已经求解过的子问题结果,在本题中,我采用了map,完成了这个记录工作,map的key是pair<i, v>,表示前i张发票在最大可报销额是v时的情况,map的value是key对应的最大报销额,如此一来可以节省不少时间。
同时在参考了网上解答的基础上,我发现了原题中有一个条件没有交代清楚,就是上述问题描述中的限制3),题目中说“单项物品的价值不得超过600元“,这可能会引起混淆。
三,算法实现
#include <iostream> // for cin, cout, endl
#include <cstdio> // for printf
#include <map> // for map
#include <utility> // for pair, make_pair
using std::cin;
using std::cout;
using std::endl;
using std::map;
using std::pair;
using std::make_pair;
void input(int&);
double compute(double, int);
void output(double&);
const int MAX_CHECK = 30; // the max num of check
map<pair<double, int>, double> cache; // cache the calculated value for later reference
double check[MAX_CHECK+1]; // hold input, +1 for the index from 1~30
int main()
{
double q;
int n;
double res; // res for result
while (cin>>q>>n && n!=0){
input(n);
res = compute(q, n);
output(res);
}
}
double max2(double a, double b)
{
if (a > b)
return a;
else
return b;
}
void input(int& n)
{
double t, p; // t for total, the total money of check; p for price, the price of each item of check
char k, s; // k for kind, the kind of check; s for skip, skip char ':'
bool valid; // indicate whether this check valid
int num; // the num of valid input
double tmp[3]; // for hold the sum of A, B, C items
int m;
int i, j;
num = 0;
for (i=0; i<n; ++i){
for (j=0; j<3; ++j)
tmp[j] = 0.0;
valid = true;
t = 0.0;
cin >> m;
for (j=0; j<m; ++j){
cin >> k >> s >> p;
if (k>='A' && k<='C')
tmp[k-'A'] += p;
else
valid = false;
t += p;
}
// check if the price of each class or of total valid
for (j=0; j<3; ++j)
if (tmp[j] > 600.0)
valid = false;
if (t > 1000.0)
valid = false;
if (valid)
check[++num] = t;
}
n = num;
}
double compute(double q, int n)
{
// n==1, return check[n] if q>=check[n], return 0 if q<check[n]
if (n <= 1){
if (q >= check[n])
return check[n];
else
return 0.0;
}
pair<double, int> put, nput; // nput for not put, these indicate whether to put check n
double put_v, nput_v; // the corresponding value of above two states
put_v = nput_v = double();
nput = make_pair(q, n-1);
nput_v = cache[nput];
if (nput_v == double()){ // if the put state has not been calculated
nput_v = compute(q, n-1);
cache[nput] = nput_v;
}
if (q >= check[n]){ // now, the F[q, n] = max(F[q-check[n], n-1], F[q, n-1])
put = make_pair(q-check[n], n-1);
put_v = cache[put];
if (put_v == double()){ // if the put state has not been calculated
put_v = compute(q-check[n], n-1) + check[n];
cache[put] = put_v;
}
}
return max2(put_v, nput_v);
}
void output(double& res)
{
printf("%.2f\n", res);
// clear the map
cache.clear();
}