1.4 买书问题
参考《编程之美–1.4 买书问题》
问题描述:
节假日时,书店会有促销活动。假设某系列书一共有5本,每本价格相同为8元。如果一次购买本系列多本不同的书可以享受折扣。折扣如下:
本书 | 折扣 |
---|---|
2 | 5% |
3 | 10% |
4 | 20% |
5 | 25% |
在每个订单中每本书绑定于一个折扣规则中,即已享受折扣的书不能再与其他书组成折扣。
要求设计出算法计算读者所购买一批书的最低价格。
问题分析:
用(x1,x2,x3,x4,x5)表示这系列第i本购买数。
贪心算法:
先考虑贪心算法。我们可以看到折扣在四本时比三本多了10%,而五本只比四本多了5%。折扣增加的幅度并不是单调不减的,这就给使用贪心算法埋下了隐患。
果不其然,当买(2,2,2,1,1)时,(1,1,1,1,1)+(1,1,1,0,0)的方案享受折扣为1.55本书的价格,而(1,1,1,1,0)+(1,1,1,0,1)的方案享受折扣为1.6本书的价格,贪心规则在此失效了。
动态规划:
解决这种问题的另一个常见方法是动态规划。动态规划是把一个复杂问题转化为多个简单的子问题。
由于五本书的价格相同,因此在这里先前的表示能够简化,用(y1,y2,y3,y4,y5)来表示要买的书的从大到小的排列。假设第一个折扣组合中要买4本书,(y1-1,y2-1,y3-1,y4,y5-1),由于y4>=y5所以在之后的折扣组合中必定会出现有第四本书而没有第五本书的情况,由于两本书价格相同,所以把这种情况中的第四本书换成第五本书,也就是说在第一个折扣中把组合中的第五本书换成第四本书。这两者是等效的。也就是说(y1-1,y2-1,y3-1,y4,y5-1)能转化为(y1-1,y2-1,y3-1,y4-1,y5)。
同理可证(y1,y2-1,y3,y4,y5)与(y1-1,y2,y3,y4,y5)等效,以此类推,折扣组合中书的数量为i,则折扣中应该是数量最多的前i本书。
状态转移方程如下:
F(y1,y2,y3,y4,y5)
= 0 if(y1=y2=y3=y4=y5=0)
= min{
5 * 8 * (1-25%) + F(y1-1,y2-1,y3-1,y4-1,y5-1), if(y5>=1)
4 * 8 * (1-20%) + F(y1-1,y2-1,y3-1,y4-1,y5-0), if(y4>=1)
3 * 8 * (1-10%) + F(y1-1,y2-1,y3-1,y4-0,y5-0), if(y3>=1)
2 * 8 * (1- 5%) + F(y1-1,y2-1,y3-0,y4-0,y5-0), if(y2>=1)
1 * 8 * (1- 0%) + F(y1-1,y2-0,y3-0,y4-0,y5-0), if(y1>=1)
}
每次获得的中间状态都需要进行从大到小的排序。
由于五本书的价格相同,这里如果通过计算最大折扣本数来获得最优方案较为简便,最大折扣本数就是通过折扣方案能够节约的书的数量。
代码如下:
#include <iostream>
#include <windows.h>
#include <cmath>
#include <cassert>
#include <climits>
using namespace std;
class Books
{
public:
Books()
{
bookKinds = 0;
}
~Books()
{
delete bookArray;
delete offs;
}
void Run(int* pbookArray, int pbookKinds, double pprice, double * poffs, int poffsKinds)
{
Init(pbookArray,pbookKinds,pprice,poffs,poffsKinds);
pay = 0;
int bookNum = 0;
for (int i = 0; i < pbookKinds; ++i) {
bookNum += pbookArray[i];
}
pay = bookNum * price - Search(pbookArray) * price;
}
void Output()
{
cout<<"pay: "<<pay<<endl;
}
private:
void Init(const int* pbookArray, int pbookKinds, double pprice, const double* poffs, int poffsKinds)
{
bookKinds = pbookKinds;
bookArray = new int[bookKinds];
for(int i = 0; i < bookKinds; i++)
{
bookArray[i] = pbookArray[i];
}
pay = 0;
price = pprice;
offs = new double[poffsKinds];
for (int i = 0; i < poffsKinds; ++i) {
offs[i] = poffs[i];
}
}
void SortArray(int* pbookArray)
{
int tmp;
for(int i = 0; i < bookKinds - 1; i++)
{
for(int j = i + 1; j < bookKinds; j++)
{
if(pbookArray[i] < pbookArray[j])
{
tmp = pbookArray[i];
pbookArray[i] = pbookArray[j];
pbookArray[j] = tmp;
}
}
}
}
bool isEmpty(const int* pbookArray)
{
for(int i = 0; i < bookKinds; i++)
{
if(pbookArray[i] != 0)
{
return false;
}
}
return true;
}
double Search(int* pbookArray)
{
if(isEmpty(pbookArray))
{
return 0;
}
int buf[bookKinds];
double changes[bookKinds];
for(int i = 0; i< bookKinds; i++)
{
changes[i] = 0;
}
SortArray(pbookArray);
for(int i = 1; i <= bookKinds; i++)
{
if(pbookArray[bookKinds-i] >= 1)
{
for(int k = 0; k < bookKinds; k++)
{
buf[k] = pbookArray[k];
}
for(int j = 0; j <= bookKinds - i; j++)
{
buf[j] = buf[j] - 1;
}
changes[i-1] = (bookKinds - i + 1) * offs[i-1] + Search(buf);
}
}
double max = 0;
for(int i = 0; i < bookKinds; i++)
{
if(changes[i] > max)
{
max = changes[i];
}
}
return max;
}
private:
int bookKinds;
int* bookArray;
double pay;
double price;
double * offs;
};
int main()
{
Books books;
int bookKinds = 5;
int bookArray[5] = {2,2,2,1,1};
double price = 8;
int offsKinds = 5;
double offs[5] = {0.25,0.2,0.1,0.05,0};
books.Run(bookArray,bookKinds,price,offs,offsKinds);
books.Output();
return 0;
}