• 第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贪心算法]

  • 相关阅读:
    旧文备份:利用一个定时器实现多个虚拟定时器的两种方法
    TypeScript type 类型别名
    TypeScript 模块系统
    github----awesome-typescript-projects
    synchronous-request-with-websockets
    async await promise
    Sharing Configuration in ASP.NET Core SPA Scenarios
    Unicode String to a UTF-8 TypedArray Buffer in JavaScript
    MVC 访问静态页面 View 下面放JS
    Processing Binary Protocols with Client-Side JavaScript
  • 原文地址:https://www.cnblogs.com/gaopeng527/p/4604079.html
Copyright © 2020-2023  润新知