代码先行,输入和函数实现分开了,便于复用代码,后面有背包九讲的解释
poj3624,这是一个超链接
下面代码中有行注释要求j从C自减到w[i],若能理解这一点为啥子,有助于理解多重背包的二进制优化。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <climits>
#include <stack>
#include <queue>
#include <vector>
#include <string>
#include <map>
#include <set>
#include <algorithm>
using namespace std;
/*------default---------*/
const int maxn = 12881;
int dp[maxn], w[maxn], v[maxn];
void ZeroOnePack(int C, int N, int W[], int V[]) {
memset(dp, 0, C * sizeof (dp));
for (int i = 1; i <= N; ++i) //每次外层循环,决定选还是不选第i个物品
for (int j = C; j >= w[i]; --j) //一定要从C到w[i],不然会有影响,单步可以看出来
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
int main()
{
int n, c;
while (~scanf("%d%d", &n, &c)) {
for (int i = 1; i <= n; ++i)
scanf("%d%d", w + i, v + i);
ZeroOnePack(c, n, w, v);
printf("%d\n", dp[c]);
}
return 0;
}
\
\
下面摘自《背包九讲》我已把此文件上传了,0积分下载哦
1 01背包问题
1.1 题目
有 N 件物品和一个容量为 V 的背包。放入第 i 件物品耗费的空间是 C i ,得到
的价值是 W i 。求解将哪些物品装入背包可使价值总和最大。
1.2 基本思路
这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不
放。
用子问题定义状态:即 F[i,v] 表示前 i 件物品恰放入一个容量为 v 的背包可以
获得的最大价值。则其状态转移方程便是:
F[i,v] = max {F[i − 1,v],F[i − 1,v − C i ] + W i }
这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生
出来的。所以有必要将它详细解释一下:“将前 i 件物品放入容量为 v 的背包
中”这个子问题,若只考虑第 i 件物品的策略(放或不放),那么就可以转化
为一个只和前 i − 1 件物品相关的问题。如果不放第 i 件物品,那么问题就转化
为“前 i − 1 件物品放入容量为 v 的背包中”,价值为 F[i − 1,v] ;如果放第 i 件物
品,那么问题就转化为“前 i − 1 件物品放入剩下的容量为 v − C i 的背包中”,
此时能获得的最大价值就是 F[i − 1,v − C i ] 再加上通过放入第 i 件物品获得的价
值 W i 。
伪代码如下:
F[0,0..V ] = 0
for i = 1 to N
for v = C i to V
F[i,v] = max {F[i − 1,v],F[i − 1,v − C i ] + W i }
1.3 优化空间复杂度
以上方法的时间和空间复杂度均为 O(V N) ,其中时间复杂度应该已经不能
再优化了,但空间复杂度却可以优化到 O(V ) 。
先考虑上面讲的基本思路如何实现,肯定是有一个主循环 i = 1..N ,每次
算出来二维数组 F[i,0..V ] 的所有值。那么,如果只用一个数组 F[0..V ] ,能不
能保证第 i 次循环结束后 F[v] 中表示的就是我们定义的状态 F[i,v] 呢? F[i,v] 是
由 F[i−1,v] 和 F[i−1,v −C i ] 两个子问题递推而来,能否保证在推 F[i,v] 时(也
即在第 i 次主循环中推 F[v] 时)能够取用 F[i − 1,v] 和 F[i − 1,v − C i ] 的值呢?事
实上,这要求在每次主循环中我们以 v = V..0 的递减顺序计算 F[v] ,这样才能保
证推 F[v] 时 F[v − C i ] 保存的是状态 F[i − 1,v − C i] 的值。伪代码如下:
F[0..V ] = 0
for i = 1 to N
for v = V to C i
F[v] = max {F[v],F[v − C i ] + W i }
其中的 F[v] = max {F[v],F[v − C i ] + W i } 一句,恰就对应于我们原来的转移方
程,因为现在的 F[v − C i ] 就相当于原来的 F[i − 1,v − C i ] 。如果将 v 的循环顺序
从上面的逆序改成顺序的话,那么则成了 F[i,v] 由 F[i,v −C i ] 推导得到,与本题
意不符。
事实上,使用一维数组解01背包的程序在后面会被多次用到,所以这里抽象
出一个处理一件01背包中的物品过程,以后的代码中直接调用不加说明。
def ZeroOnePack( F,C,W )
for v = V to C
F[v] = max (F[v],f[v − C] + W)
有了这个过程以后,01背包问题的伪代码就可以这样写:
for i = 1 to N
ZeroOnePack( F,C i ,W i )
1.4 初始化的细节问题
我们看到的求最优解的背包问题题目中,事实上有两种不太相同的问法。
有的题目要求“恰好装满背包”时的最优解,有的题目则并没有要求必须把背
包装满。一种区别这两种问法的实现方法是在初始化的时候有所不同。
如果是第一种问法,要求恰好装满背包,那么在初始化时除了 F[0] 为 0 ,其
它 F[1..V ] 均设为 −∞ ,这样就可以保证最终得到的 F[V ] 是一种恰好装满背包的
最优解。
如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该
将 F[0..V ] 全部设为 0 。
这是为什么呢?可以这样理解:初始化的 F 数组事实上就是在没有任何物
品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量
为 0 的背包可以在什么也不装且价值为 0 的情况下被“恰好装满”,其它容量的
背包均没有合法的解,属于未定义的状态,应该被赋值为-∞了。如果背包并非
必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的
价值为 0 ,所以初始时状态的值也就全部为 0 了。
这个小技巧完全可以推广到其它类型的背包问题,后面也就不再对进行状
态转移之前的初始化进行讲解。
1.5 一个常数优化
上面伪代码中的
for i = 1 to N
for v = V to C i
中第二重循环的下限可以改进。它可以被优化为
for i = 1 to N
for v = V to max (V − Σ N
i
W i ,C i )
这个优化之所以成立的原因请读者自己思考。(提示:使用二维的转移方程思
考较易。)
1.6 小结
01背包问题是最基本的背包问题,它包含了背包问题中设计状态、方程的最
基本思想。另外,别的类型的背包问题往往也可以转换成01背包问题求解。故
一定要仔细体会上面基本思路的得出方法,状态转移方程的意义,以及空间复
杂度怎样被优化。\