问题描述:
有N种物品和一个容量为V的背包。第 i 种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
问题分析:
1.初步:
多重背包最朴素的思想就是将所有的物品(不管同不同一类)都看不同的种类,进行01背包的求解。另也可以看做完全背包的变形:第 i 种物品可以取0件、取1件……取n[i]件。
for(int i = 1; i <= N; ++i)
{
for(int j = V; j >= c[i]; --j)
{
for(int k = 0; k <= n[i] && c[i]*k <= j; ++k)
{
dp[j] = max(dp[j], dp[j-k*c[i]] + k*w[i]);
}
}
}
很明显朴素的做法过于暴力,是个题都会卡这个时间或者空间的,所以需要进行二进制优化。
二进制思想:
假设有 1000 个苹果,现在要取n个苹果,如何取?朴素的做法应该是将苹果一个一个拿出来,直到n个苹果被取出来。
再假设有 1000 个苹果和10只箱子,利用箱子进行某些预工作,然后如何快速的取出n个苹果呢?So..可以在每个箱子中放 2^i (i<=0<=n)个苹果,也就是 1、2、4、8、16、32、64、128、256、489(n减完之前的数之后不足 2^i,取最后剩余的数),相当于把十进制的数用二进制来表示,取任意n个苹果时,只要推出几只箱子就可以了。
再次分析:
只看上面是不好理解的,比如:7的二进制 7 = 111, 它可以分解成 001, 010, 100. 这三个数可以组合成任意小于等于 7 的数,而且每种组合都会得到不同的数。再比如,13 = 1101, 则分解为 0001, 0010, 0100, 0110. 前三个数字可以组合成 7 以内任意一个数,每个数再加上0110(= 6) 之后可以组合成任意一个大于等于 6 小于等于 13 的数,所以依然能组成任意小于等于 13 的数,很明显 6,7 会多重复 1 次,但对于求解背包问题是没有影响的,基于这种思想把一种多件物品转换为,多件一种物品,然后用01背包求解即可。
下面证明一下为什么有重复没有影响的正确性:
不正确可能产生的原因就是将重复的多加一次,比如 7+7=14 就可能造成错误,但是分析一下其实是不可能出现的,因为假如第一个 7 是由 0001+0010+0100 得到的,那么第二个 7 就需要用到 0110+0001,但 0001 只能出现一次嘛,所以不会形成这种错误的,其它的可能错误操作也能由这种解释进行否定,从而验证了正确性。
代码1
int num[maxn][2], dp[maxn];
int N, V, c, w, n, tot;
memset(dp, 0, sizeof dp);
cin >> V >> N; tot = 1;
for(int i = 1; i <= N; ++i)
{
cin >> c >> w >> n;
for(int k = 1; k < n; k<<=1)//左移求下一个所需二进制数
{
num[tot][0] = k*c;
num[tot++][1] = k*w;
n -= k;
}
num[tot][0] = n*c;
num[tot++][1] = n*w;
}
for(int i = 1; i < tot; ++i)
{
for(int j = V; j >= num[i][0]; --j)
dp[j] = max(dp[j], dp[j-num[i][0]]+num[i][1]);
}
代码2
int num[maxn][2], dp[maxn];
int N, V, c, w, n, t;
memset(dp, 0, sizeof dp);
cin >> V >> N;
for(int i = 1; i <= N; ++i)
{
cin >> c >> w >> n;
for(int k = 1; k < n; k<<=1)
{
for(int j = V; j >= k*c; --j)
dp[j] = max(dp[j], dp[j-k*c]+k*w);
n -= k;
}
for(int j = V; j >= n*c; --j)
dp[j] = max(dp[j], dp[j-n*c]+n*w);
}
继续加油~