编程之美(六)饮料供货

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

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

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

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

分析:动态规划

每种饮料由五个关键参数来描述 S i , V i , C i , H i , B i {Si,Vi,Ci,Hi,Bi} Si,Vi,Ci,Hi,Bi (分别对应的是饮料名字、容量、可能的最大数量、满意度、实际购买量)来表示第 i i i种饮料( i = 0 , 1 , . . . , n − 1 i = 0,1, ..., n - 1 i=0,1,...,n1)

那么饮料总容量为 ∑ i = 0 n − 1 \sum_{i=0}^{n-1} i=0n1( V i ∗ B i Vi * Bi ViBi),总满意度为 ∑ i = 0 n − 1 \sum_{i=0}^{n-1} i=0n1( H i ∗ B i Hi * Bi HiBi)
那么题目的要求就是,在满足条件 ∑ i = 0 n − 1 \sum_{i=0}^{n-1} i=0n1( V i ∗ B i ) = V Vi * Bi) = V ViBi)=V的基础上,求解 m a x max max{ ∑ i = 0 n − 1 \sum_{i=0}^{n-1} i=0n1( H i ∗ B i Hi * Bi HiBi)}

考虑用动态规划求解:用 o p t ( V ′ , i ) opt(V', i) opt(V,i)表示从 i i i开始到 n − 1 n-1 n1种饮料,算出总量为 V V V‘的方案中满意度之和的最大值

  • o p t ( V , 0 ) opt(V, 0) opt(V,0)即表示我们所要求的值
  • 假设我们选择 k k k i i i种饮料,那么我们所得的满意度即为 k ∗ H [ i ] + o p t [ V − k ∗ H [ i ] ] [ i + 1 ] , o p t [ V ] [ i ] = m a x ( k ∗ H [ i ] + o p t [ V − k ∗ H [ i ] ] [ i + 1 ] ) k * H[i] + opt[V - k * H[i]][i + 1],opt[V][i] = max(k * H[i] + opt[V - k * H[i]][i + 1]) kH[i]+opt[VkH[i]][i+1]opt[V][i]=max(kH[i]+opt[VkH[i]][i+1])
  • 初始化边界条件:(1) o p t [ 0 ] [ n ] = 0 opt[0][n] = 0 opt[0][n]=0,表示容量为 0 0 0的情况下,最优化结果为 0 0 0 (2) o p t [ x ] [ n ] = − I N F opt[x][n] = -INF opt[x][n]=INF( − I N F -INF INF为负无穷大),即在容量不为 0 0 0的情况下,把最优化结果设为负无穷大,并将其作为初值
动态规划代码
int Cal(int V, int T) {
	//边界条件初始化
    opt[0][T] = 0;
    for (int i = 1; i <= V; ++i) {
        opt[i][T] = -INF;
    }
    for (int j = T - 1; j >= 0; --j) {
        for (int i = 0; i <= V; ++i) {
            opt[i][j] = -INF;
            for (int k = 0; k <= c[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;
                        opt_num[i][j] = k;  //保存选取第j种饮料的数量
                    }
                }
            }
        }
    }
    return opt[V][0];
}
备忘录法:打表

用一个表格来保存已解决的子问题的答案,并通过记忆化搜索的方式来避免计算一些不可能到达的状态

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 <= c[type]; ++i) {
        int temp = Cal1(V - i * v[type], type + 1);
        if (temp != -INF) {
            temp += i * h[type];
            if (temp > ret) {
                opt_num[V][type] = i;
                ret = temp;
            }
        }
    }
    return opt[V][type] = ret;
}
测试代码
#include <iostream>
#define MAXV 100
#define MAXT 20
#define INF 0x7fffffff
#define N 7
using namespace std;
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};

int opt[MAXV + 1][MAXT + 1];
int opt_num[MAXV + 1][MAXT + 1];

int T;
/*
int Cal(int V, int T) {
    opt[0][T] = 0;
    for (int i = 1; i <= V; ++i) {
        opt[i][T] = -INF;
    }
    for (int j = T - 1; j >= 0; --j) {
        for (int i = 0; i <= V; ++i) {
            opt[i][j] = -INF;
            for (int k = 0; k <= c[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;
                        opt_num[i][j] = k;
                    }
                }
            }
        }
    }
    return opt[V][0];
}*/

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 <= c[type]; ++i) {
        int temp = Cal1(V - i * v[type], type + 1);
        if (temp != -INF) {
            temp += i * h[type];
            if (temp > ret) {
                opt_num[V][type] = i;
                ret = temp;
            }
        }
    }
    return opt[V][type] = ret;
}
int main() {
    T = N;
    int ret = Cal1(V, 0);
    cout << "0:" << opt_num[V][0] << endl;
    int k = opt_num[V][0];
    int m = V;
    for (int i = 1; i <= T - 1; ++i) {
        int temp = opt_num[m - k * v[i - 1]][i];
        cout << i << ":" << temp << endl;
        m -= k * v[i - 1];
        k = temp;
    }
    cout << ret << endl;
    return 0;
}

测试结果:
在这里插入图片描述

贪心解法《编程之美》1.6 饮料供货——贪心解法证明
编程之美1.6 饮料供货[动态规划vs贪心算法]

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值