01背包问题

问题描述

给定 n 种物品和一个容量为 c的背包,物品 i 的重量是 wi,其价值为 vi 。
问:应该如何选择装入背包的物品,使得装入背包中的物品的总价值最大?

思路

  1. 面对每个物品,我们只有选择拿取或者不拿两种选择,不能选择装入某物品的一部分,也不能装入同一物品多次
  2. 声明一个 大小为 dp[n][c] 的二维数组,dp[ i ][ j ] 表示 在面对第 i 件物品,且背包容量为 j 时所能获得的最大价值
  3. j < w[i] 的情况,这时候背包容量不足以放下第 i 件物品,只能选择不拿
    dp[ i ][ j ] = dp[ i-1 ][ j ](因为不放第i个物品,所以当物品为i时的背包容量=当物品为i-1时的背包容量)
  4. j>=w[i] 的情况,这时背包容量可以放下第 i 件物品,我们就要考虑拿这件物品是否能获取更大的价值,即我们要不要拿这个物品。
    1)如果拿,dp[ i ][ j ]=dp[ i-1 ][ j-w[ i ] ] + v[ i ]。 这里的dp[ i-1 ][ j-w[ i ] ]指的就是考虑了i-1件物品,背包容量为j-w[i]时的最大价值,也是相当于为第i件物品腾出了w[i]的空间。同时要加上第i件物品的价值v[i]
    2)如果不拿,dp[ i ][ j ] = dp[ i-1 ][ j ] 同3.
    至于拿还是不拿,则需要比较1)和2)哪个dp[i][j]比较大
  • 可以进行空间优化,即将二维数组转换为一维数组 dp[j]=dp[j-w[i]]+v[i]dp[j]=dp[j]
    由此可以得到状态转移方程:
if(j>=w[i]){
	int value1=dp[i-1][j];
    int value2=dp[i-1][j-w[i]]+v[i];
    if(value1>value2)
    	dp[i][j]=dp[i-1][j];
    else
    	dp[i][j]=dp[i-1][j-w[i]]+v[i];
}
else
    m[i][j]=m[i-1][j];

示例

  • 如下图,是商店对五件商品(第0、1、2、3、4件)的价值与重量的表格,你有一个容量c=20,的背包,现在你想在容量内尽可能拿价值更高的商品。
    在这里插入图片描述
  • 此题的状态转移方程如下:
    在这里插入图片描述
  • 具体分析如下(逆序):
    在这里插入图片描述 - 我们可以建立一个二维数组dp[][]来存储信息,下图以表格形式存储的信息如下:
    在这里插入图片描述

代码

#include<stdio.h>
int dp[1005][1005];
int s,v[1005],w[1005],n;
int main() {
	int T,i,j;
	scanf("%d", &T);//输入循环次数,即几组测试样例
	while (T--) {
		scanf("%d %d", &n, &s);//输入商品个数和背包容量
		for (i = 0; i <= n; i++) {
			for (j = 0; j <= s; j++) {
				dp[i][j]=0;	
			}
			v[i]=0;c[i]=0;
		}
		for (i = 1; i <= n; i++) {
			scanf("%d", &v[i]);//一维数组存储商品的价值
		}
		for (i = 1; i <= n; i++) {
			scanf("%d", &w[i]);//一维数组存储商品的重量
		}
		for (i = 1; i <= n; i++) {
			for (j = s; j >= 0; j--) {//逆序
				if (c[i]>j)//如果商品重量大于当前背包容量,肯定不能拿
					dp[i][j] = dp[i-1][j];
				else{//如果未超重,则看想不想拿,哪个价值大
					int value1 = dp[i-1][j-w[i]]+v[i];//拿
					int value2 = dp[i-1][j];//不拿
					if(value1>value2)
						dp[i][j]=value1;
					else
						dp[i][j]=value2;
				}
				
			}
		}
		printf("%d\n", dp[n][s]);//输出最大价值
	}
	return 0;
}
  • 运行结果
    在这里插入图片描述
  • 如果想知道拿哪几样东西可以获得最大价值
  • 另起一个 x[ ] 数组,x[i]=0表示不拿,x[i]=1表示拿。dp[n][s]为最优值,如果dp[n][s]=dp[n-1][s] ,说明有没有第n件物品都一样,则x[n]=0 ; 否则 x[n]=1。当x[n]=0时,由x[n-1][s]继续构造最优解;当x[n]=1时,则由x[n-1][s-c[i]]继续构造最优解。以此类推,可构造出所有的最优解。
  • 代码如下:
#include<stdio.h>
int dp[1005][1005];
int x[1005],n,s,v[1005],w[1005];
void traceback()
{
    for(int i=n;i>1;i--)
    {
        if(dp[i][s]==dp[i-1][s])
            x[i]=0;
        else
        {
            x[i]=1;
            s-=w[i];
        }
    }
    x[1]=(dp[1][s]>0)?1:0;
}
int main() {
	int T,i,j;
	scanf("%d", &T);
	while (T--) {
		scanf("%d %d", &n, &s);
		for (i = 0; i <= n; i++) {
			for (j = 0; j <= s; j++) {
				dp[i][j]=0;	
			}
			v[i]=0;c[i]=0;
		}
		for (i = 1; i <= n; i++) {
			scanf("%d", &v[i]);
		}
		for (i = 1; i <= n; i++) {
			scanf("%d", &w[i]);
		}
		for (i = 1; i <= n; i++) {
			for (j = s; j >= 0; j--) {
				if (c[i]>j)
					dp[i][j] = dp[i-1][j];
				else{
					int value1 = dp[i-1][j-w[i]]+v[i];
					int value2 = dp[i-1][j];
					if(value1>value2)
						dp[i][j]=value1;
					else
						dp[i][j]=value2;
				}
				
			}
		}
		printf("%d\n", dp[n][s]);
		traceback();
    	for(int i=1;i<=n;i++)
        	printf("%d ",x[i]);
	}
	return 0;
}
  • 运行结果
    在这里插入图片描述

为什么01背包的内层循环是逆序

  • dp[i][j]只与dp[i-1][j]和dp[i-1][j-w[i]]有关,即只和i-1时刻状态有关,所以我们只需要用一维数组dp[]来保存i-1时的状态f[]。
    假设i-1时刻的dp[]为{a0,a1,a2,…,av},难么i时刻的dp[]中第j个应该为max(av,av-w[i]+v[i])即max(dp[j],dp[j-w[i]]+v[i]),
    这就需要我们遍历j时逆序遍历,这样才能保证求i时刻dp[j]时dp[j-w[i]]是i-1时刻的值。如果正序遍历则当求dp[v]时,其前面的dp[0],dp[1],…,dp[j-1]都已经改变过,里面存的都不是i-1时刻的值,这样求dp[j]时利用dp[j-w[i]]必定是错的值。最后dp[j]即为最大价值

具体请参考:https://blog.csdn.net/xiajiawei0206/article/details/19933781

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值