题目:
在节假日的时候,书店一般都会做促销活动。由于《哈利波特》系列相当畅销,店长决定通过促销活动来回馈读者。上柜的《哈利波特》平装书系列中,一共有五卷。假设每一卷单独销售均需 8 8 8欧元。如果读者一次购买不同的两卷,就可以扣除 5 5 5%的费用,三卷则更多,假设具体折扣的情况如下:
本数 | 折扣 |
---|---|
2 | 5% |
3 | 10% |
4 | 20% |
5 | 25% |
在一份订单中,根据购买的卷数及本数,就会出现可以应用不同折扣规则的情况。但是,一本书只会应用一个折扣规则。比如,读者一共买了两本卷一,一本卷二。那么,可以享受 5 5 5%的折扣。另外一本卷一则不能享受折扣。如果有多种折扣,希望计算出的总额尽可能的低。
要求根据以上需求,设计出算法,能够计算出读者所购买一批书的最低价格。
1. 贪心
对于这样的一个问题,我们很容易的带入我们的主观想法,即认为先考虑最大的折扣,然后次之,采取的策略能最省钱。那么我们按照这样的策略进行一个简单的分析,得到下列的一个折扣计算表:
本数 | 可能的分解组合 | 对应的折扣 |
---|---|---|
对于 2 − 5 2-5 2−5本书(不同卷),直接按折扣买 | 2 / 3 / 4 / 5 {2 /3 /4/ 5} 2/3/4/5 | 0.1 / 0.3 / 0.8 / 1.25 0.1/ 0.3/0.8/ 1.25 0.1/0.3/0.8/1.25 |
6 6 6 | = 5 + 1 / = 4 + 2 / = 3 + 3 / = 2 + 2 + 2 = 5 + 1/ = 4 + 2/ = 3 + 3/ =2 + 2 + 2 =5+1/=4+2/=3+3/=2+2+2 | 1.25 / 0.9 / 0.6 / 0.3 1.25/ 0.9 /0.6 / 0.3 1.25/0.9/0.6/0.3 |
7 7 7 | = 5 + 2 / = 4 + 3 / = 3 + 2 + 2 =5 + 2/ = 4 + 3/ = 3 + 2 + 2 =5+2/=4+3/=3+2+2 | 1.35 / 1.1 / 0.5 1.35 / 1.1/ 0.5 1.35/1.1/0.5 |
8 8 8 | = 5 + 3 / 4 + 4 / 3 + 3 + 2 / 2 + 2 + 2 + 2 =5 + 3/ 4 + 4/ 3 + 3 + 2/ 2 + 2 + 2 + 2 =5+3/4+4/3+3+2/2+2+2+2 | 1.55 / 1.6 / 0.7 / 0.4 1.55 / 1.6/ 0.7/ 0.4 1.55/1.6/0.7/0.4 |
9 9 9 | = 5 + 4 / = 5 + 2 + 2 / = 4 + 3 + 2 / = 3 + 3 + 3 = 5 + 4/ = 5 + 2 + 2/ = 4 + 3 + 2/ = 3 + 3 + 3 =5+4/=5+2+2/=4+3+2/=3+3+3 | 2.05 / 1.45 / 1.2 / 0.9 2.05/ 1.45/ 1.2/ 0.9 2.05/1.45/1.2/0.9 |
10 10 10 | = 5 + 5 / = 4 + 4 + 2 / 4 + 3 + 3 / = 2 + 2 + 2 + 2 + 2 / = 5 + 5/ = 4 + 4 + 2/ 4 + 3 + 3/ = 2 + 2 +2 + 2 + 2/ =5+5/=4+4+2/4+3+3/=2+2+2+2+2/ | 2.5 / 1.7 / 1.4 / 0.5 2.5/ 1.7/ 1.4/ 0.5 2.5/1.7/1.4/0.5 |
对于目前分析到的总数为 10 10 10本以下的情况,可以看到当总数为 8 8 8时,分为 5 + 3 5 + 3 5+3的情况所得折扣小于分为 4 + 4 4 + 4 4+4所得的折扣,仅这一条足以推翻该贪心策略。
书中虽然尝试着优化贪心,但始终未能找到合适的方式,于是给出了动态规划的想法
2. 动态规划
- 以 d p [ y 1 ] [ y 2 ] [ y 3 ] [ y 4 ] [ y 5 ] dp[y1][y2][y3][y4][y5] dp[y1][y2][y3][y4][y5]表示购买 y 1 y1 y1本卷一、 y 2 y2 y2本卷二、 y 3 y3 y3本卷三、 y 4 y4 y4本卷四及y5本卷五所需最少花费
- 当 y 1 = y 2 = y 3 = y 4 = y 5 = 0 y1 = y2 = y3 = y4 = y5 = 0 y1=y2=y3=y4=y5=0时,也就是说购买 0 0 0本书,自然所花费为 0 0 0,
- 首先保证 y 1 > = y 2 > = y 3 > = y 4 > = y 5 y1 >= y2 >= y3 >= y4 >= y5 y1>=y2>=y3>=y4>=y5(减少相同的情况) (1)当 y 5 > = 1 y5 >= 1 y5>=1时,考虑选择 5 5 5本书的最大折扣 + 剩余书的最少花费,即 d p [ y 1 ] [ y 2 ] [ y 3 ] [ y 4 ] [ y 5 ] dp[y1][y2][y3][y4][y5] dp[y1][y2][y3][y4][y5] = 5 ∗ 8 ∗ ( 1 − 25 5 * 8 * (1 - 25 5∗8∗(1−25%) + d p [ y 1 − 1 ] [ y 2 − 1 ] [ y 3 − 1 ] [ y 4 − 1 ] [ y 5 − 1 ] dp[y1 - 1][y2 - 1][y3 - 1][y4 - 1][y5 - 1] dp[y1−1][y2−1][y3−1][y4−1][y5−1] (2)当 y 4 > = 1 y4 >= 1 y4>=1时,考虑选择 4 4 4本书的最大折扣 + 剩余书的最少花费,即 d p [ y 1 ] [ y 2 ] [ y 3 ] [ y 4 ] [ y 5 ] dp[y1][y2][y3][y4][y5] dp[y1][y2][y3][y4][y5] = 4 ∗ 8 ∗ ( 1 − 20 4 * 8 * (1 - 20 4∗8∗(1−20%) + d p [ y 1 − 1 ] [ y 2 − 1 ] [ y 3 − 1 ] [ y 4 − 1 ] [ y 5 ] dp[y1 - 1][y2 - 1][y3 - 1][y4 - 1][y5] dp[y1−1][y2−1][y3−1][y4−1][y5]…其余几种选项同理,那么对于当前的 y 1 , y 2 , y 3 , y 4 , y 5 y1,y2,y3,y4,y5 y1,y2,y3,y4,y5来说,既然有这几种可能,就选择其中最少的花费作为它们的最少花费
//状态转移方程
dp[y1][y2][y3][y4][y5]
= 0 if (y1 = y2 = y3 = y4 = y5 = 0)
= min {
5 * 8 * (1 - 25%) + dp[y1 - 1][y2 - 1][y3 -1][y4 - 1][y5 - 1], if(y5 >= 1)
4 * 8 * (1 - 20%) + dp[y1 - 1][y2 - 1][y3 - 1][y4 - 1][y5], if (y4 >= 1)
3 * 8 * (1 - 10%) + dp[y1 - 1][y2 - 1][y3 - 1][y4][y5], if (y3 >= 1)
2 * 8 * (1 - 5 %) + dp[y1 - 1][y2 - 1][y3][y4][y5], if (y2 >= 1)
8 + dp[y1 - 1][y2][y3][y4][y5], if (y1 >= 1)
}
3. 实现代码
非递归实现动态规划:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
#define price 8
#define INF 9999
int book[5];
double discount[4] = {0.75, 0.8, 0.9, 0.95};
double dp[10][10][10][10][10];
double min_price(double t1, double t2, double t3, double t4, double t5) {
return min(min(min(min(t1, t2), t3), t4), t5);
}
int main() {
for (int i = 0; i < 5; ++i) {
cin >> book[i];
}
memset(dp, 0, sizeof(dp));
sort(book, book + 5, greater<int>());
for (int i = 0; i <= book[4]; ++i) {
for (int j = i; j <= book[3]; ++j) {
for (int k = j; k <= book[2]; ++k) {
for (int l = k; l <= book[1]; ++l) {
for (int m = l; m <= book[0]; ++m) {
//初始条件,这一点卡了很长时间
if (i + j + k + m + l == 0)
continue;
double t1 = INF, t2 = INF, t3 = INF, t4 = INF, t5 = INF;
if (i >= 1) {
int num[5] = {m - 1, l - 1, k - 1, j - 1, i - 1};
sort(num, num + 5, greater<int>());
t1 = 5 * price * discount[0] + dp[num[0]][num[1]][num[2]][num[3]][num[4]];
}
if (j >= 1) {
int num[5] = {m - 1, l - 1, k - 1, j - 1, i};
sort(num, num + 5, greater<int>());
t2 = 4 * price * discount[1] + dp[num[0]][num[1]][num[2]][num[3]][num[4]];
}
if (k >= 1) {
int num[5] = {m - 1, l - 1, k - 1, j, i};
sort(num, num + 5, greater<int>());
t3 = 3 * price * discount[2] + dp[num[0]][num[1]][num[2]][num[3]][num[4]];
}
if (l >= 1) {
int num[5] = {m - 1, l - 1, k, j, i};
sort(num, num + 5, greater<int>());
t4 = 2 * price * discount[3] + dp[num[0]][num[1]][num[2]][num[3]][num[4]];
}
if (m >= 1) {
int num[5] = {m - 1, l, k, j, i};
sort(num, num + 5, greater<int>());
t5 = price + dp[num[0]][num[1]][num[2]][num[3]][num[4]];
}
dp[m][l][k][j][i] = min_price(t1, t2, t3, t4, t5);
}
}
}
}
}
cout << dp[book[0]][book[1]][book[2]][book[3]][book[4]] << endl;
return 0;
}
递归实现动态规划:
#include <iostream>
#include <algorithm>
using namespace std;
#define price 8
#define INF 9999
int book[5];
double discount[4] = {0.75, 0.8, 0.9, 0.95};
double min_price(double t1, double t2, double t3, double t4, double t5) {
return min(min(min(min(t1, t2), t3), t4), t5);
}
double F(int y1, int y2, int y3, int y4, int y5) {
if (y1 + y2 + y3 + y4 + y5 == 0)
return 0;
int num[5] = {y1, y2, y3, y4, y5};
sort(num, num + 5, greater<int>());
double t1 = INF, t2 = INF, t3 = INF, t4 = INF, t5 = INF;
if (num[4] >= 1) t1 = 5 * price * discount[0] + F(num[0] - 1, num[1] - 1, num[2] - 1, num[3] - 1, num[4] - 1);
if (num[3] >= 1) t2 = 4 * price * discount[1] + F(num[0] - 1, num[1] - 1, num[2] - 1, num[3] - 1, num[4]);
if (num[2] >= 1) t3 = 3 * price * discount[2] + F(num[0] - 1, num[1] - 1, num[2] - 1, num[3], num[4]);
if (num[1] >= 1) t4 = 2 * price * discount[3] + F(num[0] - 1, num[1] - 1, num[2], num[3], num[4]);
if (num[0] >= 1) t5 = price + F(num[0] - 1, num[1], num[2], num[3], num[4]);
return min_price(t1, t2, t3, t4, t5);
}
int main() {
for (int i = 0; i < 5; ++i) {
cin >> book[i];
}
sort(book, book + 5, greater<int>());
cout << F(book[0], book[1], book[2], book[3], book[4]) << endl;
return 0;
}
测试样例:
//1
{1, 0, 0, 0, 0}
//2
{2, 2, 2, 1, 1}