背包问题2
描述
n个物品,每个物品有一个体积v和价值w。现在你要回答,把一个物品丢弃后,剩下的物品装进一个大小为V的背包里能得到的最大价值是多少。
输入
输入的第一行包含一个正整数n(n ≤ 5000)。
接下来n行,每行包含两个正整数v和w(v,w ≤ 5000),分别表示一个物品的体积和价值。
接下来一行包含一个正整数q(q ≤ 5000),表示询问个数。
接下来q行,每行包含两个正整数V和x(V ≤ 5000,x ≤ n),表示询问将物品x丢弃以后剩下的物品装进一个大小为V的背包能得到的最大价值。
输出
输出q行,每行包含一个整数,表示询问的答案。
样例输入
3
3 5
2 2
1 2
3
3 1
3 2
3 3
样例输出
4
5
5
样例解释
有3个物品,第一个物品的体积为3、价值为5,第二个物品体积为2、价值为2,第三个物品体积为1、价值为2。
有3个询问:
第一个询问是问去掉1物品后剩下的2、3物品填进一个大小为3的背包能得到的最大价值。显然2、3物品都是可以放进背包的,所以最大价值为2+2=4。
第二个询问是问去掉2物品后剩下的1、3物品填进一个大小为3的背包能得到的最大价值。若我们填3物品,我们只能得到价值2;若我们填1物品,则可以得到价值5。所以最大价值为5。
第三个询问我们同样也是填1物品,最大价值为5。
限制
对于30%的数据,n,q,v,V,w ≤ 10;
对于50%的数据,n,q,v,V,w ≤ 200。
时间:10 sec
空间:512 MB
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
思路
这是一个01背包问题的扩展习题。设有 Q 次询问,如果每次询问都排除掉一种物品然后应用求解01背包问题的方法求解,那么算法的复杂度为 O(QN2V)。其中,N 为物品种类数,V 为背包的最大容量。按照题目中的数据范围,为 O(50004),会超时。仔细想想,其实按照以上算法,仍然有很多重复计算的部分。例如:共 3 种物品,背包最大容量为 5,第一次询问要求排除第 2 种物品,第二次询问要求排除第 3 中物品。设 f (i, j) 表示在第 1 到第 i 种物品中选,装入容量为 j 的背包能得到的最大价值。那么在求解第一次询问时会计算 f (1, 1), ..., f (1, 5),而求解第二次询问时又会计算一遍 f (1, 1), ..., f (1, 5),这就造成了没必要的开销。
为了避免上面的问题,可以这样设计算法。设共有 N 种物品,第 i 种物品的体积为 vol[i]、价值为 value[i],最大的可能背包容量为 V。我们定义:
(1) pre[i][j]:从第 1 到第 i 种物品中选,装入容量为 j 的背包能获得的最大价值,i = 1, 2, ..., N,j = 0, 1, ..., V;
(2) post[i][j]:从第 i 到第 n 种物品中选,装入容量为 j 的背包能获得的最大价值,i = 1, 2, ..., N,j = 0, 1, ..., V。
(1) (2) 的求解其实都是01背包问题。状态转移方程为:
(1) pre[i][j] = pre[i-1][j],0 ≤ j < vol[i];
pre[i][j] = max(pre[i-1][j], pre[i-1][j-vol[i]]+value[i]),vol[i] ≤ j ≤ V 。
(2) post[i][j] = post[i+1][j],0 ≤ j < vol[i];
post[i][j] = max(post[i+1][j], post[i+1][j-vol[i]]+value[i]),vol[i] ≤ j ≤ V 。
若对于某次询问,背包容量为 v(0 < v ≤ V),排除的物品种类为 m(1 ≤ m ≤ N),则问题的解为:max( pre[m-1][0] + post[m+1][v], pre[m-1][1] + post[m+1][v-1], ..., pre[m-1][v] + post[m+1][0] )。注意,令 pre[0][·],pre[·][0],post[n+1][·],post[·][0] 均为0。
按照该方法求解,复杂度为 O(QNV)。
C++代码
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int MAXN = 5000; // 物品最大种类数 const int MAXVOL = 5000; // 背包最大容量 const int MAXQ = 5000; // 最多的询问次数 int vol[MAXVOL+5], value[MAXN+5]; // vol[i]:第i件物品的体积;value[i]:第i件物品的价值 int qV[MAXQ+5], qX[MAXQ+5]; // qV[i]:第i次询问的背包容量;qX[i]:第i次询问不包含第几种物品 int pre[MAXN+5][MAXVOL+5]; // pre[i][j]:从第1~i件物品中选取,放入容量为j的背包中的最大价值。 int post[MAXN+5][MAXVOL+5]; // post[i][j]:从第i~n件物品中选取,放入容量为j的背包中的最大价值。 /* 从若干件物品中取出某件装入有限容量的背包中可以获得最大价值。 n件物品的体积存放在 vol[1,n] 中,价值存放在 value[1,n] 中; q次询问的背包容量存放在 qV[1,q] 中,排除物品序号存放在 qX[1,q] 中, 返回一个向量 ans,ans[i] 为第 i 次询问的结果。 */ vector<int> maxValue(int n, int q) { memset(pre, 0, sizeof(pre)); memset(post, 0, sizeof(post)); vector<int> ans; ans.resize(q+1); for ( int i = 1; i <= n; ++i ) { for ( int j = 1; j < vol[i]; ++j ) pre[i][j] = pre[i-1][j]; for ( int j = vol[i]; j <= MAXVOL; ++j ) pre[i][j] = max(pre[i-1][j], pre[i-1][j-vol[i]]+value[i]); } for ( int i = n; i >= 1; --i ) { for ( int j = 1; j < vol[i]; ++j ) post[i][j] = post[i+1][j]; for ( int j = vol[i]; j <= MAXVOL; ++j ) post[i][j] = max(post[i+1][j], post[i+1][j-vol[i]]+value[i]); } for ( int i = 1; i <= q; ++i ) { int mx = 0; for ( int j = 0; j <= qV[i]; ++j ) mx = max(mx, pre[qX[i]-1][j] + post[qX[i]+1][qV[i]-j]); ans[i] = mx; } return ans; } int main() { int n = 0, q = 0; // 读入各物品体积和价值 scanf("%d", &n); for ( int i = 1; i <= n; ++i ) scanf("%d %d", vol+i, value+i); // 读入各次询问的背包容量和排除的物品种类 scanf("%d", &q); for ( int i = 1; i <= q; ++i ) scanf("%d %d", qV+i, qX+i); vector<int> ans = maxValue(n, q); for ( int i = 1; i <= q; ++i ) printf("%d\n", ans[i]); return 0; }