题目链接:点击这里
完全背包问题描述:有
种物品和一个容量为
的背包,每种物品都有无限件可用。放入第
种物品的费用是
,价值是
。求解:将哪些物品装入背包,可使这些物品的耗费的费用总和不超过背包容量,且价值总和最大。
【基本思路】
这个问题非常类似于 01 背包问题,所不同的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取 件、取 件、取 件……直至取 件等许多种。
如果仍然按照解 01 背包时的思路,令
表示前
种物品恰放入一个容量为
的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方程,像这样:
这跟 01 背包问题一样有 个状态需要求解,但求解每个状态的时间已经不是常数了,求解状态 的时间是 ,总的复杂度可以认为是 ,是比较大的。
这说明 01 背包问题的方程的确是很重要,可以推及其它类型的背包问题。但我们还是要试图改进这个复杂度。
【O(NV) 的算法】
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 1010;
int c[maxn], w[maxn]; //c是物品的体积,w是物品的价值
int dp[maxn];
int main()
{
int N, V;
scanf("%d%d", &N, &V);
for(int i = 1; i <= N; ++i)
scanf("%d%d", &c[i], &w[i]);
for(int i = 1; i <= N; ++i)
{
for(int j = c[i]; j <= V; ++j)
{
dp[j] = max(dp[j], dp[j-c[i]]+w[i]);
}
}
printf("%d", dp[V]);
return 0;
}
会发现,这个代码与 01 背包问题的代码只有 的循环次序不同而已。
为什么这个算法就可行呢?
首先想想为什么 01 背包中要按照 递减的次序来循环。让 递减是为了保证第 次循环中的状态 是由状态 递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第 件物品”这件策略时,依据的是一个绝无已经选入第 件物品的子结果 。
而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第 种物品”这种策略时,却正需要一个可能已选入第 种物品的子结果 ,所以就可以并且必须采用 递增的顺序循环。这就是这个简单的程序为何成立的道理。
值得一提的是,上面的伪代码中两层 for 循环的次序可以颠倒。这个结论有可能会带来算法时间常数上的优化。