编程之美(四)买书问题

博客围绕书店《哈利波特》促销活动,要求设计算法计算读者购书最低价格。先尝试贪心策略,发现总数为8时该策略不适用。后采用动态规划,以dp数组表示购买不同卷数书的最少花费,并给出状态转移方程,最后提及非递归和递归实现代码及测试样例。

题目:

在节假日的时候,书店一般都会做促销活动。由于《哈利波特》系列相当畅销,店长决定通过促销活动来回馈读者。上柜的《哈利波特》平装书系列中,一共有五卷。假设每一卷单独销售均需888欧元。如果读者一次购买不同的两卷,就可以扣除555%的费用,三卷则更多,假设具体折扣的情况如下:

本数折扣
25%
310%
420%
525%

在一份订单中,根据购买的卷数及本数,就会出现可以应用不同折扣规则的情况。但是,一本书只会应用一个折扣规则。比如,读者一共买了两本卷一,一本卷二。那么,可以享受555%的折扣。另外一本卷一则不能享受折扣。如果有多种折扣,希望计算出的总额尽可能的低。

要求根据以上需求,设计出算法,能够计算出读者所购买一批书的最低价格。

1. 贪心

对于这样的一个问题,我们很容易的带入我们的主观想法,即认为先考虑最大的折扣,然后次之,采取的策略能最省钱。那么我们按照这样的策略进行一个简单的分析,得到下列的一个折扣计算表:

本数可能的分解组合对应的折扣
对于2−52-525本书(不同卷),直接按折扣买2/3/4/5{2 /3 /4/ 5}2/3/4/50.1/0.3/0.8/1.250.1/ 0.3/0.8/ 1.250.1/0.3/0.8/1.25
666=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+21.25/0.9/0.6/0.31.25/ 0.9 /0.6 / 0.31.25/0.9/0.6/0.3
777=5+2/=4+3/=3+2+2=5 + 2/ = 4 + 3/ = 3 + 2 + 2=5+2/=4+3/=3+2+21.35/1.1/0.51.35 / 1.1/ 0.51.35/1.1/0.5
888=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+21.55/1.6/0.7/0.41.55 / 1.6/ 0.7/ 0.41.55/1.6/0.7/0.4
999=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+32.05/1.45/1.2/0.92.05/ 1.45/ 1.2/ 0.92.05/1.45/1.2/0.9
101010=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.52.5/ 1.7/ 1.4/ 0.52.5/1.7/1.4/0.5

对于目前分析到的总数为101010本以下的情况,可以看到当总数为888时,分为5+35 + 35+3的情况所得折扣小于分为4+44 + 44+4所得的折扣,仅这一条足以推翻该贪心策略。

书中虽然尝试着优化贪心,但始终未能找到合适的方式,于是给出了动态规划的想法

2. 动态规划
  • dp[y1][y2][y3][y4][y5]dp[y1][y2][y3][y4][y5]dp[y1][y2][y3][y4][y5]表示购买y1y1y1本卷一、y2y2y2本卷二、y3y3y3本卷三、y4y4y4本卷四及y5本卷五所需最少花费
  • y1=y2=y3=y4=y5=0y1 = y2 = y3 = y4 = y5 = 0y1=y2=y3=y4=y5=0时,也就是说购买000本书,自然所花费为000
  • 首先保证y1>=y2>=y3>=y4>=y5y1 >= y2 >= y3 >= y4 >= y5y1>=y2>=y3>=y4>=y5(减少相同的情况) (1)当y5>=1y5 >= 1y5>=1时,考虑选择555本书的最大折扣 + 剩余书的最少花费,即dp[y1][y2][y3][y4][y5]dp[y1][y2][y3][y4][y5]dp[y1][y2][y3][y4][y5] = 5∗8∗(1−255 * 8 * (1 - 2558(125%) + dp[y1−1][y2−1][y3−1][y4−1][y5−1]dp[y1 - 1][y2 - 1][y3 - 1][y4 - 1][y5 - 1]dp[y11][y21][y31][y41][y51] (2)当y4>=1y4 >= 1y4>=1时,考虑选择444本书的最大折扣 + 剩余书的最少花费,即dp[y1][y2][y3][y4][y5]dp[y1][y2][y3][y4][y5]dp[y1][y2][y3][y4][y5] = 4∗8∗(1−204 * 8 * (1 - 2048(120%) + dp[y1−1][y2−1][y3−1][y4−1][y5]dp[y1 - 1][y2 - 1][y3 - 1][y4 - 1][y5]dp[y11][y21][y31][y41][y5]…其余几种选项同理,那么对于当前的y1,y2,y3,y4,y5y1,y2,y3,y4,y5y1,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}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值