编程之美:第一章 1.6饮料供货

/*
饮料供货:
大家对每一种饮料的满意度是知道的。微软供应部每天供应总量为V的饮料。每种饮料的单个容量都是2的方幂,比如王老吉,都是2^3 = 8升的,可乐都是
2^5 = 32升的。每种饮料也有购买量的上限。统计数据中用饮料名字,容量,数量,满意度描述每一种饮料。
如何求出保证最大满意度的购买量?

分析:
假设STC提供n种饮料,用(Si,Vi,Ci,Hi,Bi)(对应的是饮料的名字,容量,可能的最大数量,满意胡,实际购买量)
来表示第i种饮料(i = 0 ,1 ,..., n-1),其中可能的最大数量值STC存货的上限。
饮料总容量为:对Vi*Bi累积求和
总满意度为:对Hi*Bi累积求和
题目转化为在总容量为V的情况下,求总满意度最大的情况。这是最优问题。

看到最优,首先想到动态规划。用Opt(V',i)表示从第i,i+1,i+2,...,n-1(起始下标为0)种饮料中,
算出总量为V'的方案中满意度之和的最大值。
因此Opt(V,0)就是我们要求的值
如下推导公式:
Opt(V',i) = max{k*Hi + Opt(V' - Vi*k,i+1)}
(k = 0 ,1,,...,Ci,i = 0,1,..,n-1)
最优结果 = “选择k个第i种饮料的满意度 + 剩下部分不考虑第i种饮料的最优化结果”的最大值。
边界条件:
Opt(0,n) = 0
Opt(x,n) = -INF(x != 0)(-INF为负无穷大),即在容量不为0的情况下, 把最优化结果设为负无穷大,并把它作为
初值。

0-1背包:从后向前
0-1无限背包:从前向后
0-1多重背包:从前向后


输入:
总容积 所有饮料种类
输出:
最大满意度

解法2:
动态规划法的变形是备忘录法,通过使用表格保存已经解决的子问题的答案,通过记忆化搜索避免计算不可能状态
。具体实现:为每个子问题建立一个记录项,初始化,给记录项存入一个特殊值,表示该子问题尚未求解。
求解时,对每个待求解子问题,首先查看其对应的记录项,若是初始值,则表示该子问题第一次遇到,求解并
保存。若不是初始值,直接提取答案即可。
*/

/*
关键
*/

#include <stdio.h>
#include <string.h>
const int MAXSIZE = 1000;
int dp[MAXSIZE][MAXSIZE];
const int INT_MAX = 0x7fffffff;
const int INT_MIN = INT_MAX * -1;

typedef struct Beverage
{
 char _sName[50];//饮料名
 int _iVol;//容积
 int _iMaxNum;//最大选取数量
 int _iStatis;//满意度
 int _iBuy;//实际购买数量
}Beverage;


int max(int a,int b)
{
 return a > b ? a : b;
}

//函数流程:首先设定dp[0][T] = 0,dp[i][T] = -INF,从第T-1种饮料开始,向前遍历,对每种体积从0到V的
//情况设定opt[i][j] = -INF,对遍历到第某种饮料时,遍历其选取的瓶数,如果此时总体积<瓶数乘以*每瓶的
//容量,就退出,相当于这部分没有选取。余下的情况就是总体积>瓶数乘以数量时,逆推,满意度 = 
//dp[i - k*V[j]][j+1],判断状态是否可得到,不可得,剪枝过滤,如果客的,则满意度要累加上
//瓶数乘以满意度,并且与原有dp[i][j]做比较,如果打,才进行更新,否则,不更新
int package(int V,int T)
{
 dp[0][T] = 0;//总体积为0的情况下,从第T种,到第T-1种饮料中,算出的满意度最大值
 for(int i = 1 ; i <= V ; i++ )//设定其他体积下,从第T种,到第T-1种饮料中,算出的满意度最大值为负无穷
 {
  dp[i][T] = INT_MIN;
 }
 for(int j = T-1 ; j >= 0 ; j--)
 {
  for(int i = 0 ; i <= V ; i++)
  {
   dp[i][j] = INT_MIN;
   for(int k = 0 ; k <= C[j] ; k++)//(Si,Vi,Ci,Hi,Bi),遍历第j种饮料的选取数量k
   {
    if(i < k*V[j])//如果总体积,小于选取的第j种饮料的总体积,那么直接遍历下一个体积
    {
     break;
    }
    if(INT_MIN != dp[i-k*V[j]][j+1])//总体积减去第j个物体的体积,选择的是从j+1,...T-1物体总
     //体积在i-k*V[j]时所能得到的最大满意度,如果该状态是可以得到的
    {
     dp[i][j] = max(dp[i][j],dp[i-k*V[j]][j+1] + k*H[j]);
    }
   }
  }
 }
 return dp[V][0];//空间复杂度为V*T,dp[V][T],时间复杂度为O(V*N*max(Ci))
}

//子问题的记录项表,假设从i到T种饮料中,找出容量综合为V'的一个方案,满意度最多能够达到opt(V',i,T-1),
//存储于opt[V'][i]。初始化时opt中存储值为-1,表示该子问题尚未求解。
int _opt[MAXSIZE][MAXSIZE];
int T;
int cal(int V,int type)//type表示第几种饮料
{
 if(type == T)
 {
  if(V == 0)
  {
   return 0;
  }
  else
  {
   return INT_MIN;
  }
 }
 if(V < 0)
 {
  return INT_MIN;//这是不可能的
 }
 else if(V == 0)
 {
  return 0;//因为opt[0][j] = -INF
 }
 else if(_opt[V][type] != -1)//如果已经求解了子问题,就不再求解
 {
  return _opt[V][type];
 }

 int ret = INT_MIN;
 for(int i = 0; i <= C[type] ; i++)//选取的饮料数量
 {
  int iTemp = cal(V - i*V[type],type+1);//递归求解
  if(iTemp != INT_MIN)
  {
   iTemp += i*H[type];
   if(iTemp > ret)
   {
    ret = iTemp;
   }
  }
 }
 return _opt[V][type] = ret;
}

void process()
{
 memset(_opt,-1,sizeof(_opt));
}

int main(int argc,char* argv[])
{
 process();
 getchar();
 return 0;
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值