题意:
给出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;
}
}
}