背包问题

各种背包
背包问题大概分下面这几种
*部分背包问题
*0 1 背包问题
*完全背包问题
*多重背包问题

分析
因为物体既有重量又有价值,所以不能简单的先拿轻的(轻的可能价值也小),也能先拿价值大的(它可能特别重),而因该综合考虑两个因素。一种直观的贪心策略就是:优先拿“价值除以重量”最大的,直到重量和正好为W

思路
那么你作为一个非常优秀的ACMer,肯定应该知道按照什么顺序拿物品的吧。没错,看着值钱的先抢!
解决
那么问题很简单咯,把” 值钱” 的东西排在前面,每次拿抢的时候,问问看背包君够不够承受得住,承受的了,就全部抢过来。承受不住,那么只能按照所能承受的重量,取物品的一部分了。当然价值也得按照比例来哦

0 1 背包问题
有n 种重量和价值分别为wivi 的物品,从这些物品中挑选总重量不超过W 的物品,求出挑选物品价值总和的最大值。在这里,每种物品只可以挑选一件
限制条件
1 <= n <= 100
1 <= wi; vi <= 100
1 <= W <= 10000

分析一波
这道题就是各个物品“选”与“不选”的组合,因此被称为01 背包问题。如果检查n 个物品所有“选”与“不选”的组合,算法的复杂度为O(2n) 当物品的大小以及背包的大小均为正数,则01 背包问题可以用动态规划法以O(nW) 的效率解决。

状态转移方程
如果我们按照如下的方式来定义递推关系的话,刚刚关于i 的循环就能正向进行我们定义dp[i+1][j]:= 从0 到i 这i+1 个物品中选出总重量不超过j 的物品时总价值的最大值很显然dp[0][j]=0,因为你没物品可取的时候背包的价值为0
                                       dp[i][j] (j < w[i])

dp[i + 1][j] ={ 
                                       max(dp[i][j]; dp[i][j -w[i]] + v[i])         其他

int dp[MAXN][MAXN]//dp[i][j]表示当背包剩余空间为j时,放第i个物品时最高的价值
for(int i=0;i<n;i++)//n为物品个数,w为背包的容量
    for(int j=0;j<=W;j++){
        if(j<w[i])
            dp[i+1][j]=dp[i][j];
        else
            dp[i+1][j]=max(dp[i][j],dp[i][j-w[i]]+v[i]);
    }
printf("%d\n",dp[n][W]);//二维数组





int dp[MAXN];//背包容量为j时的最大价值
for(int i=0 ; i<n ; i++)
    for(int j=w ; j>=0 ; j--){//逆序,保证dp[v-w[i]]保存的状态是dp[i-1][v-w[i]];
        if(w[i]<=j)
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]); 
        else    dp[j]=dp[j];
    }
printf("%d\n",dp[w]);//一维滚动数组

完全背包问题:

问题提出
有n 种重量和价值分别为w[i]v[i] 的物品,从这些物品中挑选总重量不超过W 的物品,求出挑选物品价值总和的最大值。在这里,每种物品可以挑选任意件

限制条件
1 <= n <= 100
1 <= wi; vi <= 100
1 <= W <= 10000

这次同一类的物品可以挑选任意多件了。我们再试着写出递推关系。另dp[i + 1][j] = 从前i 种物品中挑选总重量不超过j 时总价值的最大值。那么递推关系就为:
状态转移方程
dp[0][ j ] = 0 

dp[i+1][ j ]=max(dp[ i ][ j - k*w[i] ]+k * v[ i ]  | k*w[ i ]< = w)     

 这个代码并不友好,拥有三重循环O(nW2)上边的代码有很多重复计算,将状态转移方程化简一下得到的是这样子的

状态转移方程续
这样一来就不需要k 的循环了,便可以用O(nW) 时间解决问题

int dp[MAXN][MAXN]//dp[i][j]表示当背包剩余空间为j时,放第i个物品时最高的价值
for(int i=0;i<n;i++)//n为物品个数,w为背包的容量
    for(int j=0;j<=W;j++){
        if(j<w[i])
            dp[i+1][j]=dp[i][j];
        else
            dp[i+1][j]=max(dp[i][j],dp[i+1][j-w[i]]+v[i]);
    }
printf("%d\n",dp[n][W]);//二维数组





int dp[MAXN];//背包容量为j时的最大价值
for(int i=0 ; i<n ; i++)
    for(int j=0 ; j<=w ; j++){//正序,保证dp[v-w[i]]保存的状态是dp[i][v-w[i]];因为物品可能有无限个,所以j的状态是从少放一个i那儿继承来的
        if(w[i]<=j)
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]); 
        else    dp[j]=dp[j];
    }
printf("%d\n",dp[w]);//一维滚动数组


多重背包问题
有n 种重量和价值分别为w[i],v[i] 的物品,从这些物品中挑选总重量不超过W 的物品,求出挑选物品价值总和的最大值。不过这里,每种物品最多可以挑选mi 件
限制条件
1 <= n <= 100
1 <= wi; vi <= 100
1 <= mi <= 10000
1 <= W <= 10000

状态转移方程
dp[i] [ j ]= 到第i 个物品为止总重量不超过j 的所有选法中最有可能的最大值
dp[i + 1] [ j ] = max(dp[ i ] [ j  - k * w[ i ] ] + k * v[i]  ||   0<= k<= m[ i ])
 分析分析
对第i 种物品有mi + 1 件策略:取0 件,取1 件....... 取mi 件,复杂度是O(nmW),无法在规定时间内求解           

分析分析
一种好想好写的基本方法是将其转化为01 背包问题求解:把第i 件物品换成mi 件01 背包中的物品,也就是说我们将mi 件物品变成0 ...mi 个物品,则得到的物品数为Σmi 的01 背包问题,如果直接求得话,复杂度仍然是O(nmW)    

分析分析
但是我们希望将它转换成01 背包问题之后,能够想完全背包一样降低复杂度。考虑二进制的思想,把第i 件物品换成若干件物品,使得原问题中的第i 件物品可取的每一种策略——取0...mi 件——均能够等价于取若干件物品代换之后后的物品,另外,取mi 件的策略必不能够出现

将mi 分解为如下形式:
mi = 1 + 2 + 4 + :::: + 2^k + a(0 a 2k+1)
由于1; 2; ...., 2^k 的组合可以表示出 2^(k+1) - 1 的所有整数,因此1; 2; : : : 2^k; a 可以表示出所有 mi 的所有整数。因此,我们把
mi 个重量和价值分别为wi 和vi 的物品,看成重量和价值分别为wi *x; vi* x(x = 1; 2; : : : ; 2^k; a) 的k+2 个物品。这样,物品的
总个数就变为O(n log m) 个,使用一般的01 背包DP 可以在O(nW logm) 时间内求出答案

#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;

typedef long long LL;
const int MAXN = 1e6 + 10;
LL v[110], w[110], c[110];
//多重背包:v表示价值,w表示空间,c表示个数
LL val[1010], size[1010];
//01背包:val表示新的价值,size表示新的空间 
LL dp[MAXN];

int main() { 
    int n, m;
    while(scanf("%d %d", &n, &m) != EOF) {     
        int count = 0;
        for(int i = 0; i < n; i++) {  
            scanf("%lld %lld %lld", &c[i], &w[i], &v[i]);
            for(LL j = 1; j <= c[i]; j << 1) {
                val[count] = j * v[i];
                size[count++] = j * w[i];
                c[i] -= j;
            }
            if(c[i] > 0) {
                val[count] = c[i] * v[i];
                size[count++] = c[i] * w[i];
            }
        }
        memset(dp, 0, sizeof(dp));
        for(int i = 0; i < count; ++i) {
            for(int j = m; j >= size[i]; --j) {
                dp[j] = max(dp[j], dp[j - size[i]] + val[i]);
            }
        }
        printf("%lld\n", dp[m]);
    }  
    return 0;  
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值