编程之美 - 饮料供货

问题描述

公司采购饮料,采购的饮料有一个总量的限制 V0升,同时每种饮料有最大瓶数的限制C(i),大家对每种饮料有一个满意度 H(i),问怎样采购能使总的满意度最高。注: 饮料的包装规格都是2的n次方的整数。


想法 I : 动态规划

假设:

          饮料的种类为 n
          每种饮料的购买量为 B(i)  (i  = 0.. n)
          每种饮料最大瓶数的限制  C(i)  (i  = 0.. n)
          每种饮料的满意度为  H(i)  (i  = 0.. n)
          每种饮料的包装规格  V(i)  (i  = 0.. n)
          总的 容量为 V0
          满意度为   Happy

V0 = B(0) * V(0) + B(1) * V(1) + .... + B(n) * V(n)
Happy = B(0) * H(0) + B(1) * H(1) + .... + B(n) * H(n)

求的是 Max(Happy)。

一个最优的结果  Opt(V0,n),是由中间若干个步骤的最优结果 Opt(V`,i) 推算出来的
Opt(V`,i) = MAX{   B(i)*H(i) + Opt(V`- B(i)*V(i), i-1)  }

当前步骤的最优结果 = 上一步的最优结果 + 当前步骤的结果。
对每一步都进行最优结果的推算,最后得到的一定是最优的方案。

举例:   有3种饮料,  总的容量 为  8
每一种的瓶数限制  arrC[] = {5, 4, 6};
每一种的满意度     arrH[] = {3, 5, 2};
每种的容量            arrV[] = {2, 4, 8}

代码示例:
#include <iostream>
#include <iomanip> 

using namespace std;

#define V0 8
#define T  3
#define INF 255


int arrC[] = {5, 4, 6};
int arrH[] = {3, 5, 2};
int arrV[] = {2, 4, 8};

void printOpt(int arr[V0+1][T+1], int nLeni, int nLenj)
{
    int i =0, j = 0;

    cout << "=================================" << endl;
    for (i = 0; i < nLeni; i++)
    {
        for (j = 0; j < nLenj; j++)
        {
            cout.fill(' ');  
            cout.setf(ios::right);  
            cout.width(6);  
            cout << setprecision(6) << arr[i][j] << "  ";
        }
        cout << endl;
    }
    cout << endl;
    cout << endl;
}

int Calc(int V)
{
    int i=0, j=0, k=0;
    int opt[V0+1][T+1];

    memset(opt, 0, sizeof(int) * (V0+1)*(T+1));
    opt[0][T] = 0;
    for (i = 1; i <= V0; i++)
    {
        opt[i][T] = -INF;
    }
    printOpt(opt, V0+1, T+1);

    for (j = T-1; j >=0; j--)
    {
        for (i = 0; i <= V0; i++)
        {
            opt[i][j] = -INF;
            for (k = 0; k < arrC[j]; k++)
            {
                if (i < k * arrV[j])
                    break;

                int h = opt[i - k * arrV[j]][j+1];
                if (h != -INF)
                {
                    h += arrH[j] * k;
                    if (h > opt[i][j])
                    {
                        opt[i][j] = h;
                        printOpt(opt, V0+1, T+1);
                    }
                }
            }
        }
    }
    printOpt(opt, V0+1, T+1);
    return opt[V0][0];
}

void main()
{
    int nHappy = 0;

    nHappy = Calc(V0);

    cout << nHappy << endl;

    cin >> nHappy;
}


运算结果:

=================================     初始化状态
     0       0       0        0
     0       0       0    -255
     0       0       0    -255
     0       0       0    -255
     0       0       0    -255
     0       0       0    -255
     0       0       0    -255
     0       0       0    -255
     0       0       0    -255


=================================
     0       0       0       0
     0       0       0    -255
     0       0       0    -255
     0       0       0    -255
     0       0       0    -255
     0       0       0    -255
     0       0       0    -255
     0       0       0    -255
     0       0       0    -255


=================================    买第3种饮料,买8升得到的 Happy
     0       0       0       0                                                     opt(8, 2) = 2
     0       0    -255    -255
     0       0    -255    -255
     0       0    -255    -255
     0       0    -255    -255
     0       0    -255    -255
     0       0    -255    -255
     0       0    -255    -255
     0       0        2    -255


=================================
     0       0         0         0
     0       0    -255    -255
     0       0    -255    -255
     0       0    -255    -255
     0       0    -255    -255
     0       0    -255    -255
     0       0    -255    -255
     0       0    -255    -255
     0       0         2    -255


=================================   买第2种饮料,买4升得到的 Happy = 5
     0         0         0         0                                              opt(4, 1) = 5
     0    -255    -255    -255
     0    -255    -255    -255
     0    -255    -255    -255
     0         5    -255    -255
     0         0    -255    -255
     0         0    -255    -255
     0         0    -255    -255
     0         0          2    -255


=================================   买第2种饮料,买8升时要先和之前的第三种饮料的 8升做个比较
     0         0         0         0                                              此时第三种的饮料8升的 Happy 为 2
     0    -255    -255    -255
     0    -255    -255    -255
     0    -255    -255    -255
     0         5    -255    -255
     0    -255    -255    -255
     0    -255    -255    -255
     0    -255    -255    -255
     0          2         2    -255                                        


=================================   买第2种饮料,买8升时要先和之前的第三种饮料的 8升做个比较
     0          0         0         0                                             买第二种的饮料8升的 Happy 为 10,  10作为最优解被保留
     0    -255    -255    -255
     0    -255    -255    -255
     0    -255    -255    -255
     0          5    -255    -255
     0    -255    -255    -255
     0    -255    -255    -255
     0    -255    -255    -255
     0         10         2    -255                                             opt(8, 2) = 2  vs  opt(8, 1) = 10


=================================
     0          0         0         0
     0    -255    -255    -255
     0    -255    -255    -255
     0    -255    -255    -255
     0          5    -255    -255
     0    -255    -255    -255
     0    -255    -255    -255
     0    -255    -255    -255
     0        10         2    -255


=================================   买第1种饮料,买2升得到的 Happy = 3
     0            0         0          0
  -255    -255    -255    -255
        3    -255    -255    -255                                          opt(2, 0) = 3
       0    -255    -255    -255
       0          5    -255    -255
       0    -255    -255    -255
       0    -255    -255    -255
       0    -255    -255    -255
       0        10         2    -255


=================================  买第1种饮料,买4升,要和 买第2种饮料 买4升的happy进行比较
     0            0         0         0  
  -255    -255    -255    -255
       3     -255    -255    -255
  -255    -255    -255    -255
        5          5    -255    -255                                          opt(4, 1) = 5
       0    -255    -255    -255
       0    -255    -255    -255
       0    -255    -255    -255
       0        10         2    -255


=================================    opt(4, 0) = 6  第1种 4升的方案被保留
     0            0         0         0
  -255    -255    -255    -255
       3     -255    -255    -255
  -255    -255    -255    -255
        6          5    -255    -255                                            opt(4, 1)  vs  opt(4, 0)  6 > 5    6 被保留
       0    -255    -255    -255
       0    -255    -255    -255
       0    -255    -255    -255
       0        10         2    -255


=================================     取6升时,可以取2升第1种饮料和取4升第2种,他们的
     0            0         0         0                                            Happy 和 = 3 + 5 = 8
  -255    -255    -255    -255
        3     -255    -255    -255
  -255    -255    -255    -255
       6           5     -255    -255
   -255    -255   -255    -255
        8     -255    -255    -255
       0     -255    -255    -255
       0         10         2    -255


=================================       取6升时,也可以取6升第1种饮料 Happy = 3 * 3 = 9
     0            0         0         0
  -255    -255    -255    -255
       3     -255    -255    -255
  -255    -255    -255    -255
        6         5    -255    -255
   -255    -255    -255    -255
         9    -255    -255    -255                
        0    -255    -255    -255
        0        10         2    -255


=================================
     0            0         0         0
  -255    -255    -255    -255
       3     -255    -255    -255
  -255    -255    -255    -255
       6          5    -255    -255
   -255    -255    -255    -255                                            win
        9     -255    -255    -255                                         opt(6, 0)  vs  opt(2, 0) + opt(4, 1)  
   -255    -255    -255    -255
       10       10         2    -255


=================================   取6升时,可以取4升第1种饮料和取4升第2种,他们的
     0            0         0         0                                           Happy 和 = 6 + 5 = 11
  -255    -255    -255    -255
       3     -255    -255    -255
  -255    -255    -255    -255
        6           5    -255    -255
   -255    -255    -255    -255
        9     -255    -255    -255
   -255    -255    -255    -255
        11       10         2    -255


=================================   取6升时,也可以取8升第1种饮料  Happy = 3 * 4 = 12
     0            0         0         0
  -255    -255    -255    -255
       3     -255    -255    -255
  -255    -255    -255    -255
       6          5    -255    -255
   -255    -255    -255    -255
        9     -255    -255    -255
   -255    -255    -255    -255
        12       10         2    -255


=================================
     0            0         0         0
  -255    -255    -255    -255
       3     -255    -255    -255
  -255    -255    -255    -255
       6          5    -255    -255
   -255    -255    -255    -255
        9     -255    -255    -255                         
   -255    -255    -255    -255                                            win
        12       10         2    -255                                        opt(8, 0)   vs  opt(4, 0) + opt(4, 1) 


12

空间复杂度:使用了一个二维数组 V * T -->  总的升数 * 总的饮料种类
时间复杂度:使用了3 个for 循环嵌套   V * T * Max(Ci)   -->  总的升数 * 总的饮料种类 * 找到当前种类饮料最大解的时间


想法 II : 动态规划方式的一种变形,通过增加一个状态标记,判断当前升数-种类的饮料是否被计算过来提高效率。
用 -1 标记当前升数,当前种类的饮料-当前升数,还没有被计算过。用-INF表示当前升数-当前种类的饮料是无效的。


程序示例:
#include <iostream>
#include <iomanip> 

using namespace std;

#define V0 8
#define T  3
#define INF 255


//int arrC[] = {5, 4, 6};
//int arrH[] = {3, 5, 2};
//int arrV[] = {2, 4, 8};

int arrC[] = {5, 4, 6};
int arrH[] = {1, 5, 2};
int arrV[] = {2, 4, 8};


void printOpt(int arr[V0+1][T+1], int nLeni, int nLenj)
{
    int i =0, j = 0;

    cout << "=================================" << endl;
    for (i = 0; i < nLeni; i++)
    {
        for (j = 0; j < nLenj; j++)
        {
            cout.fill(' ');  
            cout.setf(ios::right);  
            cout.width(6);  
            cout << setprecision(6) << arr[i][j] << "  ";
        }

        cout << endl;
    }
    cout << endl;
    cout << endl;
}


int opt[V0+1][T+1];
int calc(int V, int type)
{
    int nRet = -INF;
    int i = 0;
    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];

    for (i = 0; i < arrC[type]; i++)
    {
        if (V - i * arrV[type] < 0)
            continue;

        int nTmp = calc(V - i * arrV[type], type+1);
        if (nTmp != -INF)
        {
            nTmp += arrH[type] * i;
            if (nTmp > nRet)
            {
                nRet = nTmp;
            }
        }
    }

    opt[V][type] = nRet;
    printOpt(opt, V0+1, T+1);
    return nRet;
}


void main()
{
    int i = 0;
    memset(opt, -1, 4*(V0+1)*(T+1));
    i = calc(V0, 0);

    cout << i;

    cin >> i;
}


测试结果:

=================================
    -1      -1      -1      -1
    -1      -1      -1      -1
    -1      -1      -1      -1
    -1      -1      -1      -1
    -1      -1      -1      -1
    -1      -1      -1      -1
    -1      -1      -1      -1
    -1      -1      -1      -1
    -1      -1        2      -1


=================================
    -1      -1      -1      -1
    -1      -1      -1      -1
    -1      -1      -1      -1
    -1      -1      -1      -1
    -1      -1     -255      -1
    -1      -1      -1      -1
    -1      -1      -1      -1
    -1      -1      -1      -1
    -1      -1        2      -1


=================================
    -1      -1      -1      -1
    -1      -1      -1      -1
    -1      -1      -1      -1
    -1      -1      -1      -1
    -1      -1     -255      -1
    -1      -1      -1      -1
    -1      -1      -1      -1
    -1      -1      -1      -1
    -1       10       2      -1


=================================
    -1      -1      -1      -1
    -1      -1      -1      -1
    -1      -1      -1      -1
    -1      -1      -1      -1
    -1      -1     -255      -1
    -1      -1      -1      -1
    -1      -1     -255      -1
    -1      -1      -1      -1
    -1       10       2      -1


=================================
    -1      -1      -1      -1
    -1      -1      -1      -1
    -1      -1     -255      -1
    -1      -1      -1      -1
    -1      -1     -255      -1
    -1      -1      -1      -1
    -1      -1     -255      -1
    -1      -1      -1      -1
    -1       10       2      -1


=================================
    -1      -1      -1      -1
    -1      -1      -1      -1
    -1      -1   -255      -1
    -1      -1      -1      -1
    -1      -1   -255      -1
    -1      -1      -1      -1
    -1     -255   -255    -1
    -1      -1      -1      -1
    -1       10       2      -1


=================================
    -1      -1      -1      -1
    -1      -1      -1      -1
    -1      -1     -255    -1
    -1      -1      -1      -1
    -1        5     -255    -1
    -1      -1      -1      -1
    -1     -255   -255    -1
    -1      -1      -1      -1
    -1       10       2      -1


=================================
    -1      -1      -1      -1
    -1      -1      -1      -1
    -1     -255  -255    -1
    -1      -1      -1      -1
    -1        5    -255     -1
    -1      -1      -1      -1
    -1     -255  -255    -1
    -1      -1      -1      -1
    -1       10       2      -1


=================================
    -1      -1      -1      -1
    -1      -1      -1      -1
    -1     -255  -255    -1
    -1      -1      -1      -1
    -1        5    -255    -1
    -1      -1      -1      -1
    -1     -255  -255    -1
    -1      -1      -1      -1
     10      10       2      -1


10




你可能感兴趣的:(编程之美 - 饮料供货)