poj 1276 多重背包问题

题意:

给出N种货币,每种货币有a[i]张,

对于给定的钱数X,求出货币可以拼出的最接近于并且小于等于X的数值


解法:

把钱的上限X看作体积,并且每种货币的面值既是背包问题里面的“体积”也是“价值

相对于0-1背包问题,该问题属于多重背包问题(每组个数有限数量不一),

比较好写的解法是转换为0-1背包,该方法的时间复杂度是O(V*Σn[i]),对于1276这道题会TLE

分组的解法:


对于每一组物品

我们考虑把第i种物品换成若干件物品,使得原问题中第i种物品可取的每种策略——取0..n[i]件——均能等价于取若干件代换以后的物品。

另外,取超过n[i]件的策略必不能出现。

方法是:将第i种物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数。

使这些系数分别为1,2,4,...,2^(k-1),n[i]-2^k+1,且k是满足n[i]-2^k+1>0的最大整数。

例如,如果n[i]为13,就将这种物品分成系数分别为1,2,4,6的四件物品。

设k为所求的k,num为给定的该组的个数,下面的程序计算k的值

			int k = 0;
			int temp = 1;
			while (1)
			{
				if (temp - 1 >= num)
					break;
				temp *= 2;
				k++;
			}
			k = k - 1;

计算得出k以后对该组重新分配的物品计算对应的系数,设该组物品价值为val,结果存入fee数组,

第一个系数必须为1,后面的按照上述方法计算

			//分组
			int at = 1;
			for (i = 0; i < k + 1; i++)
			{

				if (i != 0)
				{
					if (i == k)
					{
						at = num - at * 2 + 1;
					}
					else
						at *= 2;
				}
				fee[cnt++] = at * val;
			}

之后重新运用0-1经典背包和滚动数组方法求解

#include <iostream>
#include <vector>
#include <map>
#include <list>
#include <set>
#include <deque>
#include <stack>
#include <queue>
#include <algorithm>
#include <cmath>
#include <cctype>
#include <cstdio>
#include <iomanip>
#include <cmath>
#include <cstdio>
#include <iostream>
#include <string>
#include <sstream>
#include <cstring>
#include <queue>
using namespace std;

///宏定义
const int  INF = 990000000;
const int MAXN = 100000;
const int maxn = 110000 ;
///全局变量 和 函数
int max(int a, int b)
{
	return a > b ? a : b;
}

//int T;
int N;
int V;
int f[maxn];
int fee[maxn];
int main()
{
	///变量定义
	int i, j, m;
	while(scanf("%d", &V) != EOF)
	{
		scanf("%d", &N);
		int totnum = 0;
		int cnt = 0;
		for (int kk = 0;kk < N; kk++)
		{
			int num, val; //该组的个数和该组的价值
			scanf("%d %d", &num, &val);
			if (num == 0)
				continue;
			int k = 0;
			int temp = 1;
			while (1)
			{
				if (temp - 1 >= num)
					break;
				temp *= 2;
				k++;
			}
			k = k - 1;
			//分组
			int at = 1;
			for (i = 0; i < k + 1; i++)
			{

				if (i != 0)
				{
					if (i == k)
					{
						at = num - at * 2 + 1;
					}
					else
						at *= 2;
				}
				fee[cnt++] = at * val;
			}
		}

		memset(f, 0, sizeof(f));
		for (i = 0; i < cnt; i++)
		{
			for (j = V; j >= 0; j--)
			{
				if (j >= fee[i])
				{
					if (f[j] < f[j - fee[i]] + fee[i])
					{
						f[j] = f[j - fee[i]] + fee[i];
					}
				}
			}
		}

		int ans = f[V];
		printf("%d\n", ans);
	}

	///结束
	return 0;
}


分组的可重用程序

int fee[maxn];
int cnt;
void init(int n)
{
	int i, j;
	cnt = 0;
	for (int kk = 0; kk < n; kk++) //进行分组,存入fee数组
	{
		int num = m;
		int k = 0;
		int temp = 1;
		while (1)
		{
			if (temp - 1 >= m)
				break;
			temp *= 2;
			k++;
		}
		k = k - 1;
		
		//分组
		int at = 1;
		for (i = 0; i < k + 1; i++)
		{
			
			if (i != 0)
			{
				if (i == k)
				{
					at = num - at * 2 + 1;
				}
				else
					at *= 2;
			}
			fee[cnt++] = at * kk;
		}
	}
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值