在微软亚洲研究院上班,大家早上来的第一件事是干啥呢?查看邮件? 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,...,n−1)
那么饮料总容量为
∑
i
=
0
n
−
1
\sum_{i=0}^{n-1}
∑i=0n−1(
V
i
∗
B
i
Vi * Bi
Vi∗Bi),总满意度为
∑
i
=
0
n
−
1
\sum_{i=0}^{n-1}
∑i=0n−1(
H
i
∗
B
i
Hi * Bi
Hi∗Bi)
那么题目的要求就是,在满足条件
∑
i
=
0
n
−
1
\sum_{i=0}^{n-1}
∑i=0n−1(
V
i
∗
B
i
)
=
V
Vi * Bi) = V
Vi∗Bi)=V的基础上,求解
m
a
x
max
max{
∑
i
=
0
n
−
1
\sum_{i=0}^{n-1}
∑i=0n−1(
H
i
∗
B
i
Hi * Bi
Hi∗Bi)}
考虑用动态规划求解:用 o p t ( V ′ , i ) opt(V', i) opt(V′,i)表示从 i i i开始到 n − 1 n-1 n−1种饮料,算出总量为 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]) k∗H[i]+opt[V−k∗H[i]][i+1],opt[V][i]=max(k∗H[i]+opt[V−k∗H[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;
}
测试结果: