POJ1276 多重背包(01背包 完全背包)
Posted wtyuan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了POJ1276 多重背包(01背包 完全背包)相关的知识,希望对你有一定的参考价值。
POJ1276
题目
多重背包模板题,给定背包容量(V),给定(N)种物品,每种物品的个数(n_i)、体积(v_i)和重量(w_i)已知,求背包能装下的物品的最大重量。对应本题就是,给定提款的金额cash,给定(N)种钱币,每种钱币的个数为(n_i)、面额(D_i)已知,求能兑换的钱币的最大值。本题中,体积和重量都等于面额。
Sample Input
735 3 4 125 6 5 3 350
633 4 500 30 6 100 1 5 0 1
735 0
0 3 10 100 10 50 10 10
Sample Output
735
630
0
0
算法思路
刚开始的想法是直接转化为01背包问题来解,即将第(i)种物品换成(n_i)件01背包中的物品,则得到了物品数为(sum{n_i})的01背包问题,直接求解复杂度为(mathcal{O}(Vsum{n_i})),测试表明会TLE。
需要对以上的朴素想法进行优化。将第(i)件物品参考二进制的思想来拆分,即拆分乘若干件01背包中的物品,每件物品的系数分别为(2^0, 2^1, 2^2,cdots,2^{k-1},n_i-2^k+1),(k)是满足(n_i-2^k+1>0)的最大整数。例如,若(n_i=13),则可以拆分为系数分别为(1,2,4,6)四件物品。拆分的核心出发点就是,原先的(1cdots n_i)之间的任意整数都能用相应的系数组合而成。这样的话,复杂度就可以降低为(mathcal{O}(Vsum{lceil log{n_i} ceil}))。
另外,若(n_i imes v_i>V)时,针对物品(i)可以看作完全背包问题,可以用完全背包的复杂度为(mathcal{O}(NV))算法求解物品(i)。
直接转化为01背包求解
Result: TLE
#include <stdio.h>
#include <string.h>
#include <algorithm>
int cash, N;
int denominations[10 * 1000 + 5];
int opt[100000 + 5];
int main() {
while (scanf("%d", &cash) != EOF) {
memset(opt, 0, sizeof(opt));
scanf("%d", &N);
int n, D, ptr = 1;
for (int i = 1; i <= N; i++)
{
scanf("%d %d", &n, &D);
for (int j = 1; j <= n; j++)
denominations[ptr++] = D;
}
for (int i = 1; i < ptr; i++)
for (int j = cash; j >= denominations[i]; j--)
opt[j] = std::max(opt[j], opt[j - denominations[i]] + denominations[i]);
printf("%d
", opt[cash]);
}
return 0;
}
二进制转化+完全背包优化
程序中01背包和完全背包都优化了空间复杂度,只需要定义opt
为一维数组即可。二维数组opt
到一维数组opt
的空间优化没那么好理解,加一点注解。
01背包状态转移方程:
[
egin{equation}
opt[i][j] = max(opt[i-1][j], opt[i-1][j-v_i] + w_i)
end{equation}
]
对于ZeroOnePack
函数的注解:
若外层正处于第i
次循环,内层循环中,j
逆序减小,计算opt[j]
的值时,opt[j]
、opt[j-cost]
存储都是i-1
次循环的值,即分别对应于(1)式中的(opt[i-1][j])、(opt[i-1][j-v_i])。而如果j
正序增大的话,计算opt[j]
时,opt[j-cost]
已经赋过值,存储是i
次循环的值,与状态转移方程不符。
完全背包状态转移方程:
[
egin{equation}
opt[i][j] = max(opt[i-1][j], opt[i][j-v_i] + w_i)
end{equation}
]
对于CompletaPack
函数的注解:
若外层正处于第i
次循环,内层循环中,j
正序增大,计算opt[j]
的值时,opt[j]
存储的是i-1
次循环的值,即对应(2)式中的(opt[i - 1][j])。opt[j-cost]
在本次循环中已经赋过值,存储的是第i
次循环的值,对应于(2)式中的(opt[i][j-v_i]),与状态转移方程相符。opt[j] = std::max(opt[j], opt[j - cost] + weight)
这个表达式表示,我正在考虑是否往背包中加一件物品i
,而(opt[i][j-v_i])中可能已经包含了若干件物品i
,现在考虑是否再增加物品i
,所以这一点上可以反映完全背包每种物品有无穷多的性质,也正是CompletePack
正确的原因。
Result: 700kB, 32ms
#include <stdio.h>
#include <string.h>
#include <algorithm>
int cash, N;
int opt[100000 + 5];
void CompletaPack(int cost, int weight) {//原理见上文注解
for (int j = cost; j <= cash; j++)
opt[j] = std::max(opt[j], opt[j - cost] + weight);
}
void ZeroOnePack(int cost, int weight) {//原理见上文注解
for (int j = cash; j >= cost; j--)
opt[j] = std::max(opt[j], opt[j - cost] + weight);
}
void MultiplePack(int cost, int weight, int num) {
if (cost * num >= cash) {
CompletaPack(cost, weight);
return;
}
int k = 1;
while (k < num) {//二进制拆分物品
ZeroOnePack(k * cost, k * weight);
num -= k;
k *= 2;
}
ZeroOnePack(num * cost, num * weight);
return;
}
int main() {
while (scanf("%d", &cash) != EOF) {
memset(opt, 0, sizeof(opt));
scanf("%d", &N);
int num, denomination;
for (int i = 1; i <= N; i++)
{
scanf("%d %d", &num, &denomination);
MultiplePack(denomination, denomination, num);
}
printf("%d
", opt[cash]);
}
return 0;
}
参考:
[1] 背包问题九讲 2.0
以上是关于POJ1276 多重背包(01背包 完全背包)的主要内容,如果未能解决你的问题,请参考以下文章