买书问题
题目:在节假日的时候,一般书店都会做促销,假设一套书共有5册,没册书的定价都是相同的30元,店家为了促销推出了一个方案,具体如下:
这里的折扣是每本书都享受这个折扣,当然前提是购买不同的册,比如买10本第一册,那是不会有折扣的;买两本第一册,一本第二册,那其中的有一本第一册是不能享受折扣的。现在需要你设计一个程序来计算出一个读者买一批书所需的最低价格。
解题思路:
1、贪心算法
贪心算法基本操作就是:我每次都选择最大的折扣就行了。仔细想想这个折扣方案支持的最大本数就是买不同的5册,其实要我们考虑的就是买5—10本的情况。5本以下直接按最大折扣买就行了,10本以上也可以拆分为2-10本内的多个组合。
由此按照贪心算法列出下表:
上表6-10本的前提是每次购买都有不同的5册。
贪心算法,每次进行能享受的最大折扣,这里在买8本书时出了问题,贪心算法的指引是5+3,折扣是1.55,但4+4的组合折扣是1.6.4,所以这里贪心算法走到这就算是不成立了。
2、动态规划(穷举)
想走捷径使用贪心算法走不通了,那么老老实实使用穷举法吧。动态规划的中心思想是:把大问题分解为小问题,只考虑当前怎么办。对于本问题就是列出当前轮的所有买书情况。
为了方便说明,假如我们要买3第一册、2本第二册、1本第三册、4本第四册、5本第五册,那么我们表示为(X3, X2, X1, X4, X5),花的钱表示为F(X3, X2, X1, X4, X5),由于每本书的价格是一样的,故F(X3, X2, X1, X4, X5)和F(X1, X2, X3, X4, X5)花的钱肯定是一样的,所有下面我们都按大到小的顺序排列本数。
假如现在我们要买的书为(Y1, Y 2, Y 3, Y 4, Y 5),这里的下表不代表册的数目,但是册数是按大到小排列的,也就是Y1册>=Y2册>=Y3册>= Y 4册>=Y5册。在假设Y5册>=1。
好吧我们来开始买书吧。
等等,买5本以下的时候就出现了不同的组合了,选择哪一个组合继续下去分解呢?当然可以把每种组合的情况都穷举出来,但是仔细分析一下似乎先把本数多的买了,留下来的组合会多一些。
也就例如买4本数时,是否(Y1-1, Y2-1, Y3-1, Y4-1, Y5)是最好的组合选择,假设如果我们选择(Y1-1, Y2-1, Y3-1, Y4, Y5-1)得到了最终的最优解。能证明选取(Y1-1, Y2-1, Y3-1, Y4-1, Y5)组合进行分解也能得到最优解,那么我们每次都可以使用先买数目多的这种策略。
Y4>= Y5,无非就是两种情况Y4= Y5或者Y4>Y5,如果Y4= Y5的话选取(Y1-1, Y2-1, Y3-1, Y4-1, Y5)或(Y1-1, Y2-1, Y3-1, Y4, Y5-1)进行分解肯定情况都是一样的。
如果Y4>Y5的话,首先按照(Y1-1, Y2-1, Y3-1, Y4, Y5-1)这个组合进行分解下去,我们每次没册肯定只能买一本,那么到第四册只剩最后一本的时候,第五本肯定就没有了,也就是肯定会出现某个组合中有第四册,而没有第五册。因为Y4 > Y5-1嘛。这样我们总可以把有第四册而没有第五册的这种组合中的第四册换成第五册,替换之后的情况(Y1-1, Y2-1, Y3-1, Y4-1, Y5)进行分解后能到的分解情况。
换个理解方式,假设现在有3本第四册,2本第五册。先买一本第四册剩余四五册数目(2,2);先买一本第五册剩余四五册数目(3,1)。那么四五册数目为(3,1)构成的组合,(2,2)也能达到。
好了这样我们就必须列出多有组合了,每次我们都按先买本书多的那一册来选择组合。
找出穷举中最小值就是我们的最优解了,状态方程为。
示例代码:
#include <iostream>
// 排序 从大到小
template <typename T>
void rerank(T m[], int length)//冒泡排序
{
for (int i = length - 1; i > 0; i--)
{
for (int j = 0; j < i; j++)
{
if (m[j] < m[j + 1])
{
T temp;
temp = m[j + 1];
m[j + 1] = m[j];
m[j] = temp;
}
}
}
}
template <typename T>
double minValue(T a, T b, T c, T d, T e)
{
T min = a;
if (min > b)
min = b;
if (min > c)
min = c;
if (min > d)
min = d;
if (min > e)
min = e;
return min;
}
double buy(int book[5])
{
// 书买完了
if (!(book[0]|| book[1] || book[2] || book[3] || book[4]))
return 0;
double y1 =1000000, y2 = 1000000, y3= 1000000, y4= 1000000, y0= 1000000;
rerank(book, 5);
int temp[5];
// 本次是否可以买5册不同的书
memcpy(temp, book, sizeof(temp));
if (temp[4] >= 1)
{
temp[0]--;
temp[1]--;
temp[2]--;
temp[3]--;
temp[4]--;
y4 = 5 * 10 * 0.75 + buy(temp);
}
// 本次是否可以买4册不同的书
memcpy(temp, book, sizeof(temp));
if (temp[3] >= 1)
{
temp[0]--;
temp[1]--;
temp[2]--;
temp[3]--;
y3= 4 * 10 * 0.8 + buy(temp);
}
// 本次是否可以买3册不同的书
memcpy(temp, book, sizeof(temp));
if (temp[2] >= 1)
{
temp[0]--;
temp[1]--;
temp[2]--;
y2 = 3 * 10 * 0.9 + buy(temp);
}
// 本次是否可以买2册不同的书
memcpy(temp, book, sizeof(temp));
if (temp[1] >= 1)
{
temp[0]--;
temp[1]--;
y1 = 2 * 10 * 0.95 + buy(temp);
}
// 本次是否可以买1册
memcpy(temp, book, sizeof(temp));
if (temp[0] >= 1)
{
temp[0]--;
y0 = 10 + buy(temp);
}
// 返回本轮是按买5本还是4本还是3本还是2本还是1本分解下去得到价钱的最小值
return minValue<double>(y0, y1, y2, y3, y4);
}
int main()
{
int book[] = {2,2,2,1,1};
rerank(book, 5);
std::cout << "price is: " << buy(book) << std::endl;
getchar();
return 0;
}