第1章 游戏之乐——饮料供货

饮料供货

1. 问题描述

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

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

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

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

我们先把这个问题“数学化”一下吧。

假设STC共提供n种饮料,用(SiViCiHiBi)(对应的是饮料名字、容量、可能的最大数量、满意度、实际购买量)来表示第i种饮料(= 0, 1,…, n-1),其中可能的最大数量指如果仅买某种饮料的最大可能数量。

【解法一】动态规划

饮料供货是一个求最优解问题。需要在给定最大容量V的前提下,从不同容量不同满意度的饮料中选择满意度最大的集合。

动态规划

动态规划是最常用的解决最优化问题的方法,很容易应用到本题的需求中。用opt[V,i]表示从第i,i+1,i+2,...,n-1种饮料中,算出总量为V的方案中满意度之和的最大值。

动态规划方程为:opt[V,i] = max{k*Hi + opt[V-k*Vi, i+1]}

根据以上的推到公式,就可以写出如下的动态规划求解代码,如下所示:

package chapter1youxizhileDrinkSupply;
/**
 * 饮料供货【解法一】
 * @author DELL
 *
 */
public class DrinkSupply1 {
    private int[] count;  //对应饮料种类的最大可能数量
    private int[] h;    //对应各种饮料的满意度
    private int[] V;   //对应各种饮料的容量
    private static final int INF = 1000000;
    //构造函数
    public DrinkSupply1(int[] count, int[] h){
        this.count = count;
        this.h = h;
    }
    /**
     * 计算所给方案的满意度之和的最大值
     * @param v 饮料总容量
     * @param T    饮料的种类数
     * @return
     */
    public int cal(int v, int T){
        int i,j,k;
        //opt[v][i]表示从i,...,t种饮料中,算出总容量为v的方案的满意度之和的最大值
        int[][] opt; 
        opt = new int[v+1][T+1];    
        opt[0][T] = 0;  //边界条件
        for(i=1;i<=v;i++){
            opt[i][T]=-INF;  //边界条件
        }
        for(j=T-1;j>=0;j--){
            for(i=0;i<=v;i++){
                opt[i][j] = -INF;
                for(k=0;k<=count[j];k++){  //遍历第j种饮料选取数量k
                    if(i<=k*V[j]){
                        break;
                    }
                    int x = opt[i-k*V[j]][j+1];
                    if(x!=-INF){
                        x += k*h[j];
                        if(x > opt[i][j]){
                            opt[i][j]=x;
                        }
                    }
                }
            }
        }
        return opt[v][0];
    }
    
    public static void main(String[] args){
        
    }
}

【解法二】备忘录法

package chapter1youxizhileDrinkSupply;
/**
 * 饮料供货【解法二】
 * 备忘录法
 * @author DELL
 *
 */
public class DrinkSupply2 {
    private int[] count;  //对应饮料种类的最大可能数量
    private int[] h;    //对应各种饮料的满意度
    private int[] V;   //对应各种饮料的容量
    private static final int INF = 1000000;
    private static final int T = 100; //饮料的最大种类数
    //构造函数
    public DrinkSupply2(int[] count, int[] h){
        this.count = count;
        this.h = h;
    }
    /**
     * 计算所给方案的满意度之和的最大值
     * @param v 饮料总容量
     * @param t    饮料的种类数
     * @return
     */
    public int cal(int v, int t){
        int i,j;
        //opt[v][i]表示从i,...,t种饮料中,算出总容量为v的方案的满意度之和的最大值
        int[][] opt; //子问题的记录项表,初始化时opt中存储值为-1,表示该子问题尚未求解
        opt = new int[v+1][T+1];
        for(i=0;i<=v;i++){ //初始化opt
            for(j=0;j<=t;j++){
                opt[i][j] = -1;
            }
        }
        if(t == T){
            if(v == 0)
                return 0;
            else
                return -INF;
        }
        if(v < 0)
            return -INF;
        else if(v == 0)
            return 0;
        else if(opt[v][t]!=-1){ //该子问题已求解,直接返回子问题的解
            return opt[v][t];  
        }
        //子问题尚未求解,则求解子问题
        int result = -INF;
        for(i=0;i<count[t];i++){
            int temp = cal(v-i*V[t],t+1);
            if(temp != INF){
                temp += i*h[t];
                if(temp>result)
                    result = temp;
            }
        }
        return opt[v][t] = result;
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub

    }

}

【解法三】 贪心算法

 实现代码如下:

package chapter1youxizhileDrinkSupply;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

/**
 * 饮料供货【解法三】
 * 贪心算法
 * @author DELL
 *
 */
public class DrinkSupply3 {
    private List<Map<Integer,List<Integer>>> listMap;
    public DrinkSupply3(){
        listMap = new ArrayList<Map<Integer, List<Integer>>>();        
        System.out.println("请输入饮料容量及该容量饮料的各个满意度:");
        int v; //临时存储获取的容量
        String[] s;//临时存放获取的满意度
        String flag;//判断是否继续输入
        do{
            System.out.print("请输入饮料容量,按增序输入:");
            Scanner input1 = new Scanner(System.in);
            v = input1.nextInt();
            System.out.print("请输入该容量的各个满意度(以逗号分隔):");
            Scanner input2 = new Scanner(System.in);
            s = input2.nextLine().split(",");
            List<Integer> list = new ArrayList<Integer>();  //存放每种容量饮料的满意度
            for(int i=0;i<s.length;i++){
                list.add(Integer.parseInt(s[i]));
            }
            Map<Integer,List<Integer>> map = new HashMap<Integer, List<Integer>>();//容量与满意度对应
            map.put(v, list);
            listMap.add(map);
            System.out.print("是否继续(y/n):");
            Scanner input3 = new Scanner(System.in);
            flag = input3.next();
        }while(flag.equalsIgnoreCase("y"));    
        mapArr = new Map[listMap.size()];
        max = new int[listMap.size()];  //用于存放买各种容量的饮料的各种组合最大满意度
        list = new ArrayList<Integer>();  //存放每种容量饮料的满意度
    }
    /**
     * 根据容量获取2的幂指数,向下取整
     * @param V 饮料容量
     */
    private int getPower(int V){
        int i = 0;
        while(Math.pow(2, i)<=V){
            i++;
        }
        return i-1;
    }
    
    /**
     * 获取一组满意度中的最大值
     * @param list 满意度列表
     * @return 最大满意度
     */
    private int maxH(List<Integer> list){
        Integer a[] = new Integer[list.size()];
        list.toArray(a);
        int max,i;
        max = a[0];
        for(i=1;i<a.length;i++){
            if(a[i]>max)
                max = a[i];
        }
        return max;
    }
    /**
     * 计算最大满意度
     * @param v 饮料的总容量单位L
     * @return 最大满意度
     */
    Map<Integer,List<Integer>> mapArr[];
    int max[];
    List<Integer> list;
    public int cal(int v){
        listMap.toArray(mapArr);
        if(v == 1){
            list = mapArr[0].get(v);
            return maxH(list);
        }
        if(v == 2){
            list = mapArr[1].get(v);
            int max2 = maxH(list);
            list = mapArr[0].get(1);
            int max1 = maxH(list);
            if(max2>=max1*2){
                return max2;
            }else
                return max1*2;
        }
        //用于计算存放买各种容量的饮料的各种组合最大满意度
        for(int i=0;i<listMap.size();i++){
            max[i]=maxH(mapArr[i].get((int)Math.pow(2, i)));
            for(int j=0;j<i;j++){
                if(Math.pow(2, i-j)*max[j]>max[i])
                    max[i]=(int) (Math.pow(2, i-j)*max[j]);
            }
        }
        int i = mapArr.length-1; //取得容量最大的饮料的指数
        int maxAll;  //最终结果
        int j;
        if(v>=Math.pow(2, i)){
            j = i;
        }else{
            j = getPower(v);
        }
        int n = (int) Math.pow(2, j);
        if(v%n==0){
            maxAll = v/n*max[j];  //整除,直接计算
        }else if(v%n-getPower(v%n)==0){
            maxAll = v/n*max[j]+max[getPower(v%n)]; //是2的整次幂
        }else{
            maxAll = v/n*max[j]+cal(v%n);
        }

        return maxAll;
    }

    public static void main(String[] args) {
        DrinkSupply3 ds = new DrinkSupply3();
        System.out.println(ds.cal(5));
    }

}

程序运行结果如下:

请输入饮料容量及该容量饮料的各个满意度:
请输入饮料容量,按增序输入:1
请输入该容量的各个满意度(以逗号分隔):4,5
是否继续(y/n):y
请输入饮料容量,按增序输入:2
请输入该容量的各个满意度(以逗号分隔):6
是否继续(y/n):y
请输入饮料容量,按增序输入:4
请输入该容量的各个满意度(以逗号分隔):10
是否继续(y/n):n
20

请输入饮料容量及该容量饮料的各个满意度:
请输入饮料容量,按增序输入:1
请输入该容量的各个满意度(以逗号分隔):4,5
是否继续(y/n):y
请输入饮料容量,按增序输入:2
请输入该容量的各个满意度(以逗号分隔):6
是否继续(y/n):y
请输入饮料容量,按增序输入:4
请输入该容量的各个满意度(以逗号分隔):10
是否继续(y/n):n
25

其它参考链接:

编程之美-饮料供货-动态规划

编程之美1.6 饮料供货[动态规划vs贪心算法]

posted @ 2015-06-27 20:53  ~风轻云淡~  阅读(280)  评论(0编辑  收藏  举报