答案_饮料供货

分析与解法

解法一

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

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

基于如上公式:

对于求最优化的问题,我们来看看动态规划能否解决。用OptV', i)表示从第i, i+1, i+2, , n-1, n种饮料中,算出总量为V'的方案中满意度之和的最大值。

因此,OptV, n)就是我们要求的值。

那么,我们可以列出如下的推导公式:Opt V', i = max { k* Hi + OptV' - Vi * k, i-1}k = 0, 1, , Ci, i =0, 1, , n-1)。

即:最优化的结果 = 选择第k种饮料×满意度+减去第k种饮料×容量的最优化结果根据这样的推导公式,我们列出如下的初始边界条件:

Opt0, n= 0,即容量为0的情况下,最优化结果为0

Optx, n= -INFx != 0)(INF为负无穷大),即在容量不为0的情况下,把最优化结果设为负无穷大,并把它作为初值。

那么,根据以上的推导公式,就不难列出动态规划求解代码,如下所示:

代码清单1-9

int Cal (int V, int type)

{

    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 += H[j] * k;

                    if(x > opt[i][j])

                    {

                        opt[i][j] = x;

                    }

                }

            }

        }

    }

    return opt[V][0];

}

在上面的算法中,空间复杂度为OV*N),时间复杂度约为OV*N*MaxCi))。

因为我们只需要得到最大的满意度,则计算opt[i][j]的时候不需要opt[i][j+2],只需要opt[i][j]opt[i][j+1],所以空间复杂度可以降为Ov)。


解法二

应用上面的动态规划法可以得到结果,那么是否有可能进一步地提高效率呢?我们知道动态规划算法的一个变形是备忘录法,备忘录法也是用一个表格来保存已解决的子问题的答案,并通过记忆化搜索来避免计算一些不可能到达的状态。具体的实现方法是为每个子问题建立一个记录项。初始化时,该纪录项存入一个特殊的值,表示该子问题尚未求解。在求解的过程中,对每个待求解的子问题,首先查看其相应的纪录项。若记录项中存储的是初始化时存入的特殊值,则表示该子问题是第一次遇到,此时计算出该子问题的解,并保存在其相应的记录项中。若记录项中存储的已不是初始化时存入的初始值,则表示该子问题已经被计算过,其相应的记录项中存储的是该子问题的解答。此时只需要从记录项中取出该子问题的解答即可。

因此,我们可以应用备忘录法来进一步提高算法的效率。

代码清单1-10

int[V + 1][T + 1] opt;      // 子问题的记录项表,假设从iT种饮料中,

                            // 找出容量总和为V’的一个方案,快乐指数最多能够达到

                            // optV'iT-1),存储于opt[V’][i]

                            // 初始化时opt中存储值为-1,表示该子问题尚未求解。

int Cal (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 = Cal (V – i * C[type], type + 1);

        if(temp != -INF)

        {

            temp += H[type] * i;

            if(temp > ret)

            ret = temp;

        }

 

    }

    return opt[V][type] = ret;

}

解法三

请注意这个题目的限制条件,看看它能否给我们一些特殊的提示。

我们把信息重新整理一下,按饮料的容量(单位为L)排序:

 Volume         TotalCount           Happiness

       2 0L                 TC0_0              H0_0

       2 0L                 TC0_1              H0_1

        ...                       ...                   ...

       20L                TC0_1              H0_n0

       2 1L                 TC1_0              H_10

        ...                       ...                    ...

  2ML             TCM_0               HM_0

        ...                       ...                    ...

如上表,我们有n0种容量为20L的饮料,它们的数量和快乐指数分别为(TC0_0H0_0),(TC0_1H0_1)……假设最大容量为2ML。一开始,如果V%21)非零,那么,我们肯定需要购买 20L 容量的饮料,至少一瓶。在这里可以使用贪心规则,购买快乐指数最高的一瓶。除去这个,我们只要再购买总量(V-20L的饮料就可以了。这时,如果我们要购买 21L 容量的饮料怎么办呢?除了 21L 容量里面快乐指数最高的,我们还应该考虑,两个容量为 20L 的饮料组合的情况。其实我们可以把剩下的容量为 20L 的饮料之快乐指数从大到小排列,并用最大的两个快乐指数组合出一个新的“容量为 2L” 的饮料。不断地这样使用贪心原则,即得解。这是不是就简单了很多呢?


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值