动态规划 - 01背包问题 - 01背包习题(背包问题2)

背包问题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 中物品。设 (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, ..., Nj = 0, 1, ..., V

(2) post[i][j]:从第 到第 n 种物品中选,装入容量为 j 的背包能获得的最大价值,i = 1, 2, ..., Nj = 0, 1, ..., V

        (1) (2) 的求解其实都是01背包问题。状态转移方程为:

(1) pre[i][j] = pre[i-1][j],0 ≤ jvol[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;
}

 

转载于:https://www.cnblogs.com/fyqq0403/p/10587111.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值