01背包[绝对看得懂版]

本文详细解析0/1背包问题,包括问题描述、解题思路及动态规划算法实现。通过实例介绍了如何从二维数组优化到一维数组,降低空间复杂度,解决更大规模的数据问题。同时列举了多个变种问题,如采药问题、购物问题等,展示了0/1背包问题的广泛应用。
摘要由CSDN通过智能技术生成

0/1背包完全详解

1.1 问题描述

有N件物品和一个大小为M的背包,以及若干物品,每种物品只有一件,大小分别为w[i],其价值分别为val[i]。问题:将哪些物品装入背包,可使得背包内的物品价值总和最大?

输入:

第一行:N M

接下来N行,每行两个整数,w[i]和val[i]。

输出:最大价值Vmax.

1.2 解题思路

由于每种物品都只有一件,所以只存在两个选择:装这个物品或者不装。(也就是0/1背包的含义)

我们定义一个数组bag [i] [v]用于储存——当背包大小为v,且选择处理完第i个物品后(前i个物品选择装或不装)的最大价值总和,那么最终的答案就是bag [n] [m]。

我们通过一个例子来推出bag [i] [v]的递推式。

有3个物品,大小价值如下表,和一个大小为3的背包。

物品/背包大小123
A,大小1,价值2
B,大小2,价值5
C,大小1,价值3

先考虑装不装A。如下表。

物品/背包大小123
A,大小1,价值2222
B,大小2,价值5
C,大小1,价值3

由于A的大小只有1,所以大小1~3的背包都可以装下它,于是更新所有值为A的价值2。

接下来考虑B。如下表。

物品/背包大小123
A,大小1,价值2222
B,大小2,价值5257
C,大小1,价值3

由于B的大小为2,所以大小为1的背包装不下它,值不变。

大小为2的背包可装下它,且值为5,比原来的2大,于是更新值为5。

大小为3的背包可装下它,且装完后剩余大小为1。

如果装B,那么价值=B的价值+未装B的、大小为1的背包的最大价值=5+2=7。

如果不装B,那么价值就是原来的值=2。在二者里取max,就能得到最大值,也就是7。

最后考虑C。如下表。

物品/背包大小123
A,大小1,价值2222
B,大小2,价值5257
C,大小1,价值3358

C的大小为1,背包1可装,值更大,更新为3。

背包2可装,如果装,那么价值=C的价值+未装C的、大小为1的背包的最大价值=3+2=5。

如果不装,值就是原来的5。二者取max即可。

背包3可装,如果装,那么价值=C的价值+未装C的、大小为2的背包的最大价值=5+3=8。

如果不装,值就是原来的7。二者取max即可。

所以最后输出的值就是8。

小结论:

我们可以发现递推公式:bag [i] [v] = max ( bag [i-1] [v] , bag [i-1] [ v - w [i] ] + val [i] )。

也就是处理第i件物品,背包大小为v时的最大价值,有两种可能。

①装入第i件物品

那么价值=该物品的价值+未装入该物品的、大小足够塞入该物品的背包的最大价值=bag [i-1] [ v - w [i] ] + val [i]。

②不装入第i件物品

那么价值就是处理i-1件物品时计算出来的值,不变。

二者取最大值即可。

1.2.1 代码:

#include <stdio.h>
int max(int a,int b)``
{
    if(a>b) return a;
    else return b;
}
int bag[1000][1000];//储存解的数组
int w[1000],val[1000];//每件物品的大小与价值
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&w[i],&val[i]);//读入数据
    for(int i=1;i<=n;i++) //从第一件物品一直处理到第i件物品
        for(int j=1;j<=m;j++) //从大小为1的背包处理到大小为m的背包
        if(j>=w[i])//塞得进去这件物品的话
        bag[i][j]=max(bag[i-1][j],bag[i-1][j-w[i]]+val[i]);//就判断是塞还是不塞比较好
        else//塞不进去就是原来的值
        bag[i][j]=bag[i-1][j];
    printf("%d\n",bag[n][m]);//全部处理完后,输出最后的答案
    return 0;
}

1.3 算法的空间优化

1.3.1 为什么要优化?

通过1.3的代码可以很容易的看出,当物品有1,000件,背包大小为1,000时,我们就需要开一个1,000,000大小的数组了,要是处理的数据再稍微大一点,就会导致数组开的过大,然后内存溢出。

所以为了解决更大的数据,空间的优化是必要的。

1.3.2 怎么优化?

我们可以发现,当我们处理第i件物品时,要用到的只是处理第i-1件物品时的数据,再之前的就用不到了。

所以,我们可以每次处理物品时,就把数据进行一次覆盖,这样二维数组就能降维到一维了。

但需要注意!降维到一维数组后,我们处理第i件物品时,就不能从大小为1的背包一直顺序处理到大小为m的背包了。为什么?因为这样的话,处理i-1件物品时的数据会在本次处理中被覆盖,而处理更大的背包时是会用到小背包的数据的。

这样就会出现——已经塞了第i件物品了,处理时就当做没塞,就会WA了。

所以我们这里进行倒序处理,因为处理小背包用不到大背包的数据,覆盖了也就覆盖了,无所谓。

1.3.3 优化代码

#include <stdio.h>
int max(int a,int b)
{
    if(a>b) return a;
    else return b;
}
int bag[1000];//降维到一维的数组
int w[1000],val[1000];//每件物品的大小与价值
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&w[i],&val[i]);//读入数据
    for(int i=1;i<=n;i++) //从第一件物品一直处理到第i件物品
        for(int j=m;j>0;j--) //倒序处理
        if(j>=w[i])//塞得进去这件物品的话
        bag[j]=max(bag[j],bag[j-w[i]]+val[i]);//就判断,处理后覆盖上一次处理的数据
        else//塞不进去就是原来的值
        bag[j]=bag[j];//这行代码完全没有意义,但是为了方便对比,留在这里
    printf("%d\n",bag[m]);//全部处理完后,输出最后的答案
    return 0;
}

优化完后,空间复杂度就从O(T*N)缩小到了O(N),大大节省了内存,也就可以处理更大的数据了。

1.4 0/1背包的常见例题及变种

1.4.1 采药问题 难度1 洛谷P1048

有M株药草,每株药草有其对应的价值及采摘该药草要花费的时间,在N时间内,求采摘的药草的最大价值。

这其实就是换了个题干的0/1背包模板题,上面的代码直接复制粘贴就可AC。

1.4.2 购物问题 难度1 洛谷P1060

你有N元钱,有M种物品,每种一件,每件物品都有对应的价格,并且你对每件物品都赋予了一个重要度(从1~5),求购买的物品的最高性价比和。

注:性价比=物品价格*重要度。

依然是换了个题干的0/1背包模板,只要把价值的计算变成物品价格*重要度即可(val[i] = w[i] * rank [i] )。

1.4.3 装箱问题 难度2 洛谷P1049

有一个容量为V的箱子,和n个物品,每个物品的体积为整数,装任意个物品,问剩余的最小体积是多少?

本题中,物品的价值与消费相同,也就是w[i]==val[i],代换进原来的代码即可。

注意最后输出的是剩余的最小体积,故应当输出V-bag[V]。

1.4.4 点菜问题 难度3 洛谷P1164

你有N元钱,有M种菜,每种只有一份,且有对应的价格。要求你把所有的钱都花光,问有多少种不同的点菜方案?

如果只有0元钱,那只有一种点菜方式——啥都不点。

接下来的递推公式为bag[money]+=bag[money - w[i] ]。

即花N元的点菜方式 = 花(N-某道菜价格)元钱的点菜方式的和。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值