HDU -- I love sneakers!(ACM Step: 3.3.2)

一、概述

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;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值