一,概述
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是pair對應的最大報銷額,如此一來可以節省不少時間。
同時在參考了網上解答的基礎上,我發現了原題中有一個條件沒有交代清楚,就是上述問題描述中的限制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();
}