背包问题(01 、完全 、多重)

目录

简单背包问题:

一、题目来源:

二、解析:

三、代码实现:

四、优化:

(I)滚动数组:

(II)一维数组:

五、推广:

完全背包问题:

一、题目来源:

二、分析:

三、代码实现:

多重背包问题:

一:题目来源:

二、分析:

三、代码实现:


二、分析:

三、代码实现:


简单背包问题:

一、题目来源:

2. 01背包问题 - AcWing题库

二、解析:

这个题是最简单的一类背包问题。题目要求一个理想的结果(找到最大价值)。

一个物品有两个性质:一是所占的体积,二是所拥有的价值,我们可以利用两个一维数组来分别存储这两个性质,利用一个二维数组把选与不选这些物品时的最优解存起来。

这里还需要用到一个知识,就是动态规划,这是一个很重要又很难的一个思想,其中最重要的就是找状态方程,也就是一个关系式。那么接下来我们来找一下这个关系:

这里n这一列代表第几个物品,v这一列代表第n个物品的体积,w这一列代表第n个物品的价值;

中间就是二维数组,其中当n为0时没有意义,可以去掉,在输入时从1开始即可,每一列表示还剩多少体积时的最优解,每一行表示选与不选这个物品的最优解。

 先分析一下第一行:

 当剩余体积为0时,我们肯定放不下去任何东西,所以第一个为0(价值);

当剩余体积为1时,只能放下去体积为1的物品,巧了,这一件物品的体积就是1,而且前面没有别的物品了所以这里就是它的价值2;

后面的以此类推;

当来到第二行的时候:

第一个空的分析同上,也就是和正上方的一格的值一样;

当来到第二个空的时候,剩余体积为2,此时 本物品的剩余体积正好为2,那我们就要考虑一下这一格里面是直接填上一格的值还是要改变(即把该物品的价值填入),很显然,我们需要一个最大的值;

由此接着分析下去,我们就得到了这个问题的一个状态方程:

f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);

到此,我们这个题就算解决了,剩下的就是代码实现。

三、代码实现:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int M = 2e3 + 10;
int n, m, f[M][M], v[M], w[M];//v[M]是体积,w[M]是价值
signed main()
{
    cin >> n >> m;
    int i, j;
    for (i = 1; i <= n; i++)
        cin >> v[i] >> w[i];
    for (i = 1; i <= n; i++)
    {
        for (j = 0; j <= m; j++)//对于二维数组的实现,我们需要每一行都全部遍历并改变
        {
            f[i][j] = f[i - 1][j];
            //我们一开始不知道剩余体积怎么样,那我们就把上一个的值直接拉下来
            //也就是说我们现在没选这个物品
            if (j >= v[i])
                f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
                //当我们知道此时剩余的体积可以容下该物品,那我们就要选这个物品并比较最优解
        }
    }
    cout << f[n][m] << endl;
}

四、优化:

(I)滚动数组:

我们可以发现,上⾯代码中的⼆维数组只会⽤到相邻的两层,当前这⼀层的值只会通过上⼀层转移过来,那么我们便可以用滚动数组来进⾏优化,那么什么是滚动数组呢?
我们知道 0&1=0 ,1&1=1,并且奇数和偶数最后⼀位分别是1和0,那么⼀个奇数&1的值便是1,偶数便是0;由此可见我们可以通过对第⼀维&1的操作来实现数组的前进,这样就好像数组在滚动,这就是滚动数组;

(II)一维数组:

我们还可以发现,⼆维的代码不仅只会⽤到相邻的两层,并且遍历时,我们只会用到本层的容量 j 的地方和容量为 j-w [i] 的数据,也就是说当前的这⼀层只会用到上⼀层的小于等于 j 数据;
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int M = 2e3 + 10;
int n,m,f[M],v[M],w[M];
signed main()
{
    cin>>n>>m;
    int i,j;
    for(i=1;i<=n;i++)
    cin>>v[i]>>w[i];
    for(i=1;i<=n;i++)
    {
        for(j=m;j>=v[i];j--)//这里从大到小遍历,保证每个物品被使用一次
        {
            if(j>=v[i])
            f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
    }
    cout<<f[m]<<endl;
}

五、推广:

这个问题可以推广到很多题目,比如采药问题(时间、价值)等等一系列具有以下特征的题型:

(1)一个物品有多种属性(常为两种);

(2)需要找到最优解;

  ......

完全背包问题:

一、题目来源:

3. 完全背包问题 - AcWing题库

二、分析:

完全背包问题和01背包问题的差别就是对于物品是否可以多次使用。

完全背包问题中的物品可以多次使用(可以无限次,只要不超过背包容量),而01背包问题中的物品不可以重复使用。

由于物品可以多次使用,所以我们在遍历的时候就不能对其进行从大到小的遍历(一维),而是进行从小到大遍历,保证物品可以多次使用。

三、代码实现:

#include <bits/stdc++.h>
using namespace std;
const int M = 2e3 + 10;
int n, m;
int v[M], w[M], f[M];
int main()
{
    int i, j;
    cin >> n >> m;
    for (i = 1; i <= n; i++)
    {
        cin >> v[i] >> w[i];
    }
    for (i = 1; i <= n; i++)
    {
        for (j = v[i]; j <= m; j++)//这里从小到大遍历,保证一个物品可使用多次
        {
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }
    cout << f[m];
    return 0;
}

多重背包问题:

一:题目来源:

4. 多重背包问题 I - AcWing题库 

二、分析:

对于多重背包问题,与其他不同点在物品的个数不唯一,也相当于可以重复使用。但是与完全背包问题不同的是,多重背包问题中物品的使用次数有限。

对于这类问题,我们可以利用完全背包的形式去做,只要对物品使用次数进行记录和限制就可以。

我们也可以利用01背包问题的形式去做,只要把物品展开,将物品的数量变为1即可。

下面对这两种形式进行实现。

三、代码实现:

利用01背包问题形式:

#include <bits/stdc++.h>
using namespace std;
const int M = 2e6;//这里要注意开的数组长度的大小,有时可能会因为数组长度太小而WA掉
int n, m;
int v[M], w[M], f[M], s[M];
int main()
{
    int i, j;
    cin >> n >> m;
    for (i = 1; i <= n; i++)
    {
        cin >> v[i] >> w[i] >> s[i];
    }
    // 将物品全部展开,数量为1,变成01背包问题,数量变为k
    int k = n;
    for (i = 1; i <= n; i++)
    {
        while (s[i] > 1)//由于物品的数量不确定,所以利用循环使物品全部展开
        {
            k++;
            v[k] = v[i];
            w[k] = w[i];
            s[i] -= 1;
        }
    }
    for (i = 1; i <= k; i++) // 现在是k个物品
    {
        for (j = m; j >= v[i]; j--)
        {
            if (j >= v[i])
                f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }
    cout << f[m];
    return 0;
}

利用完全背包问题形式:

#include <bits/stdc++.h>
using namespace std;
const int M = 2e6;
int n, m;
int v[M], w[M], f[M], s[M];
int main()
{
    int i, j, k;
    cin >> n >> m;
    for (i = 1; i <= n; i++)
    {
        cin >> v[i] >> w[i] >> s[i];
    }
    // 遍历物品
    for (i = 1; i <= n; i++) // 现在是k个物品
    {
        // 遍历背包
        for (j = m; j >= v[i]; j--)
        {
            for (k = 1; k <= s[i]; k++)//这里的k用来限制使用物品的次数
            {
                if (j >= k * v[i])
                    f[j] = max(f[j], f[j - k * v[i]] + k * w[i]);
            }
        }
    }
    cout << f[m];
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值