天天看点

编程之美——饮料供货

1、题目:在微软亚洲研究院上班,大家早上来的第一件事是干啥呢?查看邮件?No,是去水房拿饮料:酸奶,豆浆,绿茶、王老吉、咖啡、可口可乐……(当然,还是有很多同事把拿饮料当做第二件事)。

管理水房的阿姨们每天都会准备很多的饮料给大家,为了提高服务质量,她们会统计大家对每种饮料的满意度。一段时间后,阿姨们已经有了大批的数据。某天早上,当实习生小飞第一个冲进水房并一次拿了五瓶酸奶、四瓶王老吉、三瓶鲜橙多时,阿姨们逮住了他,要他帮忙。

从阿姨们统计的数据中,小飞可以知道大家对每一种饮料的满意度。阿姨们还告诉小飞,STC(Smart Tea Corp.)负责给研究院供应饮料,每天总量为V。STC很神奇,他们提供的每种饮料之单个容量都是2的方幂,比如王老吉,都是23=8升的,可乐都是25=32升的。当然STC的存货也是有限的,这会是每种饮料购买量的上限。统计数据中用饮料名字、容量、数量、满意度描述每一种饮料。

那么,小飞如何完成这个任务,求出保证最大满意度的购买量呢?

2、分析:

问题可以这样理解:假设现在仅仅购买一种饮料,那么我们可以求出此时不同购买量的满意程度,然后再添加一种饮料,添加这种饮料与原来的那种饮料有很多的购买组合,求出此时所有符合要求组合的满意程度,简单来讲:

  • 当只有一种饮料时,容易求得各种总容量对应的最大满意度;当新增加一种饮料时,将一部分容量用新饮料代替,求得新的满意度; 
  • 将新的满意度与旧满意度比较,如果新结果较大就更新。   
  • 很像最优路径选择问题。 

    3、代码与执行结果(动态规划):

    #include <iostream>
    using namespace std;
    
    #define N 7							//饮料种类
    int V=64;							//饮料的总量
    
    int v[N]={2,4,8,2,4,8,16};			//各种饮料的瓶装单位容量
    int c[N]={3,2,1,3,2,4,1};			//各种饮料的最大购买数量
    int h[N]={20,30,25,30,15,30,100};	//各种饮料的满意度
    
    struct Beverage						//饮料结构体
    {
        int volumn;
        int maxOffer;
        int satisfication;
    };
    
    //动态规划算法
    void DP(Beverage* b,int n)
    {
        int** M=new int*[V+1];
    	int*** pur=new int**[V+1];		//记录每种饮料的购买数量
    
        for(int i=0;i<=V;++i)
    	{
            M[i]=new int[n];
    		pur[i]=new int*[n];
    	}
    	for(i=0;i<=V;i++)
    		for(int j=0;j<n;j++)
    		{
    			pur[i][j]=new int[n];
    		}
    
    /* 仅仅购买第n种饮料的购买方案 */    
        for(i=0;i<=V;++i)
        {
            if(i/b[n-1].volumn>b[n-1].maxOffer)
    		{
                M[i][n-1]=b[n-1].satisfication*b[n-1].maxOffer;
    			pur[i][n-1][n-1]=b[n-1].maxOffer;
    		}
            else
    		{
                M[i][n-1]=b[n-1].satisfication*(i/b[n-1].volumn);
    			pur[i][n-1][n-1]=i/b[n-1].volumn;
    		}
        }
    /*边界条件初始化*/
        for(i=0;i<=V;++i)
            for(int j=0;j<n-1;++j)
                M[i][j]=-999;
    
    /*顺次添加余下的各种饮料,每次添加一种,构建最优方案*/
        for(int j=n-2;j>=0;--j)
        {
            for(int i=0;i<=V;++i)
            {
                for(int k=0;k<=b[j].maxOffer;++k)
                {
                    if(b[j].volumn*k<=i)
                    {
                        if(b[j].satisfication*k+M[i-b[j].volumn*k][j+1]>M[i][j])
                        {
                            M[i][j]=b[j].satisfication*k+M[i-b[j].volumn*k][j+1];
                            pur[i][j][j]=k;
    						for(int m=j+1;m<n;m++)
    						{
    							 pur[i][j][m]=pur[i-b[j].volumn*k][j+1][m];
    						}
                        }
                    }
                }
            }
        }
    
    /*测试代码*/
    	/*for(i=0; i<V+1; i++)
    	{
    		for(j=0; j<n; j++)
    		{
    			printf("%3d ",M[i][j]);
    		}
    		printf("\n");
    	}*/
    
    	cout<<"最佳满意度为:"<<M[V][0]<<endl<<endl;
    	cout<<"分配方案为:"<<endl;
    	for(i=0;i<n;i++)
    	{
    		cout<<"第 "<<i+1<<" 种饮料购买: ";
    		cout<<pur[V][0][i]<<" 瓶"<<endl;
    	}
    	cout<<endl;
    	system("pause");
        return ;
    }
    
    int main()
    {
        Beverage* beve=new Beverage[N];
    	for(int i=0;i<N;i++)
    	{
    		beve[i].maxOffer=c[i];
    		beve[i].volumn=v[i];
    		beve[i].satisfication=h[i];	
    	}
        DP(beve,N);
    
        return 0;    
    }
               
    编程之美——饮料供货

    4、简单一点的想法:

    应该在不超过最大购买数量的限制下,尽可能的购买 满意度/单位饮料 大的饮料。这样就简单明了多了。

    举例来讲:

    代码中各种饮料的 满意度/单位饮料分别为:

    10~7.5~3.125~15~3.75~3.75~6.25

    所以各种饮料的购买优先权分别为:

    2~3~7~1~5~6~4

    对照上面的code以及result可知分析无误,上下两部分可以相互佐证。

    5、查找表法code(动态规划的递归形式)

    #include <iostream>
    #define MAXV 100
    #define MAXT 20
    #define INF 0x7fffffff
    #define N 7
    using namespace std;
    
    int v[N]={2,4,8,2,4,8,16};
    int c[N]={3,2,1,3,2,4,1};
    int h[N]={20,30,25,30,15,30,100};
    
    int opt[MAXV+1][MAXT+1]; 
    // 记录每种饮料购买数量
    int opt_num[MAXV+1][MAXT+1];
    // 每种饮料的容量
    int VV[MAXT];
    // 每种饮料的容量上限(购买上限)
    int CC[MAXT];
    // 每种饮料的满意度
    int HH[MAXT];
    // 每种饮料的实际购买量
    int BB[MAXT];
    
    int T;
    
    int Cal1(int V, int type)
    {
    	// 最后一种饮料
    	if (type == T)
    	{
    		if (V==0)
    			return 0;
    		else
    			return -INF;
    	}
    
    	if (V < 0)
    		return -INF;
    	else if(V==0)
    		return 0;
    	else if(opt[V][type] != -1)
    		return opt[V][type];
    
    	int ret = -INF;
    	for (int i = 0; i <= CC[type]; i++)
    	{
    		int temp = Cal1(V-i*VV[type], type + 1);
    		if(temp != -INF)
    		{
    			temp += HH[type]*i;
    			if (temp > ret)
    			{
    				// 记录购买V升饮料,type类型的饮料的数量。
    				opt_num[V][type] = i;
    				ret = temp;
    			}
    		}
    	}
    	return opt[V][type] = ret;
    }
    
    int main()
    {	
    	// 饮料总数
    	int totle;	
    	// 总共的购买量
    	T=N;
    	totle=64;
    
    	for (int i=0; i <= T-1; i++)
    	{
    		VV[i] = v[i]; // 饮料的容量
    		CC[i] = c[i]; // 容量
    		HH[i] = h[i]; // 满意度
    	}
    	memset(opt, -1, sizeof(opt));
    	int r = Cal1(totle, 0);
    
    	// 下面输出每种饮料的实际供应量。 
    	cout << "0:" <<opt_num[totle][0] << endl;
    	// 第0种饮料的数量(单位:个)
    	int k = opt_num[totle][0];
    	// t是饮料总供应量(单位:升)
    	int mm = totle;
    	// 依次计算第1种到第T-1种饮料的实际供应量
    	for (i = 1; i <= T; i++)
    	{
    		// mm-k*VV[i-1]是剩余饮料供应量(单位:升)
    		int temp = opt_num[mm - k*VV[i-1]][i];
    		cout << i << ":"<< temp << endl;
    		mm -=k*VV[i-1];
    		// k保存当前饮料供应量
    		k = temp;
    	}
    	cout << "最大的满意度:";
    	cout << r <<endl<<endl;
    	system("pause");
    	return 0;
    }
    
               
    参考自http://snprintf.net/archives/442
    编程之美——饮料供货
    上下对比可知算法的正确性

继续阅读