http://acm.hdu.edu.cn/showproblem.php?pid=2844
这个题目是一个多重背包转化成01背包
题意: Whuacmers拥有bi个面值为ai的硬币,现在他要用这些硬币买价格不超过m的一个物品,问你最多能刚好能用硬币付钱的物品价格有几个(即该价格能用这些硬币凑出来)。
思路: 看到多重背包问题,第一时间想到的是转化为01背包来做,即我们把这个物品能选取多次当成有多个相同的物品给我们选取,复杂度是o(m*(bi的和)),根据题目给出的数据范围,这个方法的复杂度是妥妥的TLE的,我们需要对这个方法进行优化,我们可以用二进制的思想来考虑.
将第i种物品分成若干件01背包中的物品,其中每件物品有一个系数。这件物品的费用和价值均是原来的费用和价值乘以这个系数。令这些系数 分别为1,2,22…2k−1,bi−2k+1,且k是满足bi−2k+1 > 0的最大整数。例 如,如果bi为13,则相应的k = 3,这种最多取13件的物品应被分成系数分别 为1,2,4,6的四件物品。 分成的这几件物品的系数和为bi,表明不可能取多于bi件的第i种物品。另 外这种方法也能保证对于0…bi间的每一个整数,均可以用若干个系数的和表示。
下面是一个模板,我觉得写的特别好
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#define inf64 0x3f3f3f3f3f3f3f3f
#define inf 0x3f3f3f3f
using namespace std;
const int maxn = 1e6 + 10;
int num[maxn], dp[maxn], val[maxn];
int N,V;
void zero(int weight,int value)
{
for(int i=V;i>=weight;i--)
{
dp[i] = max(dp[i], dp[i - weight] + value);
}
}
void all(int weight,int value)
{
for(int i=weight;i<=V;i++)
{
dp[i] = max(dp[i], dp[i - weight] + value);
}
}
void solve()
{
int t = 1;
int ncount = 0;
for(int i=1;i<=N;i++)
{
if (num[i] * val[i] >= V) all(val[i], val[i]);
else
{
t = 1;
ncount = num[i];
while(t<=ncount)
{
zero(t*val[i], t*val[i]);
ncount -= t;
t *= 2;
}
zero(ncount*val[i], ncount*val[i]);
}
}
}
int main()
{
while(cin>>N>>V&&N!=0&&V!=0)
{
for (int i = 1; i <= N; i++) scanf("%d", &val[i]);
for (int i = 1; i <= N; i++) scanf("%d", &num[i]);
memset(dp, -inf, sizeof(dp));
dp[0] = 0;
solve();
int ans = 0;
for(int i=1;i<=V;i++)
{
if (dp[i] > 0) ans++;
//printf("dp[%d]=%d\n",i, dp[i]);
}
printf("%d\n", ans);
}
return 0;
}