一、概述
1.问题描述
现有金钱M,求从N个物品中挑选出价值最大的组合,有如下约束:
1)每个物品花费p[i],价值v[i]
2)物品共有K种类别,要求每类别至少挑出一个
2.问题链接
HDU -- I love sneakers!(ACM Step: 3.3.2)
3.问题截图
图1.1 问题截图
二、算法思路
一开始用了一种较新奇的思路,但是超出运行时间了,在参考了一些代码之后,总结出了思路二,如下所述。
以下术语,如“分组背包”等,参考自书籍“背包九讲”。
1.时间复杂度O(KMM)的思路
考虑到问题最后的解是如下形式:
第1类物品:选择若干件
第2类物品:选择若干件
...
第K类物品:选择若干件
对于每一类物品中选择的若干件来说,它们有一个总花费P,和对应的总价值V,那么,对于花费P而言,这类物品可以得到的最大价值一定是V(因为这些物品是问题的解,它们一定是最优的)。
根据上述,可以得出思路:求出每类物品在各花费下的最大值,然后从每类物品的花费最值表中挑选出一个与其他类别组合,并在这些组合中挑选一个最大值。
具体实现可以:
“求出每类物品在各花费下的最大值”,这个是0-1背包问题。
“然后从每类物品的花费最值表中挑选出一个与其他类别组合,并在这些组合中挑选一个最大值”,这个是分组背包问题,可以这样想,每个类别的花费最值表只能选择一个,相当于,每个花费是互斥的,即,每个类别是一个组,每个花费对应组内的一个物品。
综上,
01背包需要花费O(NM)的时间,每个物品都要经历M个状态的变换
分组背包要花费O(KMM)的时间,对于每个组,对于每个花费X,都要比较最多X个物品。
2.时间复杂度O(KM)的思路
设F[k,x]是装入前k类物品在花费为x下的最大价值,此时1,2,...,k-1都已经装完,第k类物品还没装完。
按照一般背包问题状态转移的推导思想,若继续装入第k类物品,假设物品i,此时有两种选择,
要么装它,对应于,F[k,x - p[i]] + v[i]
要么不装它,对应于,F[k,x]
再仔细分析这两个表达式,
装时,对应了情况:在第k类物品中选择若干件,以及物品i,加上前k类物品对应的价值
不装时,对应了情况:在第k类物品中选择若干件,不包括物品i,加上前k类物品对应的价值
可以发现,答案建立于在第k类物品中选择若干件的前提下,
但实际还少了一种情况:仅仅在第k类物品中选择物品i,加上前k类物品的价值
这对应于,F[k - 1,x - p[i]] + v[i]
三、算法实现
#include <iostream> // for cin, cout, endl
#include <algorithm> // for sort
#include <climits> // for INT_MIN
using std::cin;
using std::cout;
using std::endl;
using std::sort;
const int MAX_SNEAKERS = 100;
const int MAX_MONEY = 10000;
void input(int&);
void compute(int&, int&, int&);
void output(int&, int&);
// input data structure
struct data
{
int k; // kind
int p; // price
int v; // value
};
data d[MAX_SNEAKERS]; // hold input data
int ans[2][MAX_MONEY+1]; // the answer of problem, actually there will only use one more array in computing each kind, +1 for the 0~M
int main()
{
int M, N, K;
while(cin>>N>>M>>K){
input(N);
compute(M, N, K);
output(M, K);
}
return 0;
}
int max3(int i1, int i2, int i3)
{
int ret = i1;
if (ret < i2)
ret = i2;
if (ret < i3)
ret = i3;
return ret;
}
bool sort_by_kind(data l, data r)
{
if (l.k > r.k)
return false;
return true;
}
void input(int& N)
{
for (int i=0; i<N; ++i)
cin >> d[i].k >> d[i].p >> d[i].v;
}
void compute(int&M, int&N, int& K)
{
bool idx; // index of ans array first dimension
int i, k, m;
sort(d, d+N, sort_by_kind); // sort data by kind, ensure lower kind at first
for (i=0; i<=M; ++i)
ans[0][i] = 0; // initial value for F[0, ..]
idx = true; // ans[0][..] is used, now use ans[1][..]
i = 0; // i reference to data array
for (k=1; k<=K; ++k){
for (m=0; m<=M; ++m)
ans[idx][m] = INT_MIN; // initial value of F[k, ..]
while (d[i].k==k && i<N){
for (m=M; m>=d[i].p; --m)
ans[idx][m] = max3(ans[idx][m], ans[idx][m-d[i].p]+d[i].v, ans[!idx][m-d[i].p]+d[i].v);
++i;
}
if (ans[idx][M] < 0)
return; // this kind sneaker can not be selected, error
idx = !idx; // idx reference to the other layer ans array
}
}
void output(int& M, int& K)
{
bool idx = K%2;
if (ans[idx][M] < 0)
cout << "Impossible\n";
else
cout << ans[idx][M] << endl;
}