动态规划之01背包问题详解

01背包的概念

 首先我们要明确的是01背包的概念什么,01背包可以抽象成给我们N个物品,然后背包的体积是V,然后每一个物品都有一个价值是W,让求每一种物品在最多拿一个的情况下我们可以得到的最大价值是多少。

母题(01背包模板题)

01背包模板题
在这里插入图片描述
这个是我们的朴素算法

#include <bits/stdc++.h>
using namespace std;

const int N = 1010;
int f[N][N];
int v[N],w[N];

int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];

    for(int i=1;i<=n;i++)
    {
        for(int 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;
    return 0;
}

01背包的话我们可以优化掉一维,把二维化成一维

#include <bits/stdc++.h>
using namespace std;

const int N = 1010;
int f[N];
int v[N],w[N];

int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];

    for(int i=1;i<=n;i++)
    {
        for(int j=m;j>=v[i];j--)//我们从后往前枚举,因为如果直接删去第一维的话
        {	//我们的状态就会被更新成f[i][j-vi]+wi,但是我们需要的是f[i-1][j-vi]+wi,
        	//要是我们从前往后枚举的话j-vi肯定比j要小,所以在j之前就会被更新,
        	//此时就是i这一层的j-vi,从后往前枚举的话就不会造成这样的情况,
        	//所以我们选择从后往前枚举。
            f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
    }
    cout<<f[m]<<endl;
    return 0;
}

例题一(装箱问题)

装箱问题
题意:给我们一个箱子,它的体积为V,然后给我们n个物品,让我们任取若干个放入箱子内,然后求出最小的剩余空间。
思路:通过这个题我们可以联想到我们的母题,我们把用掉的最大空间求出来,用一开始的体积V减去我们求得的最大的空间,就可以得出答案。

#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 2e4 + 10;
int f[N];

int main()
{
     cin.tie(0)->sync_with_stdio(false);
     int n, m;
     cin >> m >> n;
     for (int i = 1; i <= n; i++)
     {
          int v;
          cin >> v;
          for (int j = m; j >= v; j--)
          {
               f[j] = max(f[j], f[j - v] + v);
          }
     }
     cout << m - f[m] << endl;
     return 0;
}

例题二(数字组合)

这个题是只考虑体积,然后把背包装满的方案数。与下面我们要讲的那个问题不同。
数字组合
题意:给我们n个数,然后让我们求从这里面找出若干个数,组成m的方案是多少。
思路:
在这里插入图片描述

#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1e5 + 10;
int f[N];

int main()
{
     cin.tie(0)->sync_with_stdio(false);
     int n, m;
     cin >> n >> m;
     f[0] = 1;
     for (int i = 0; i < n; i++)
     {
          int x;
          cin >> x;
          for (int j = m; j >= x; j--)
          {
               f[j] += f[j - x];
          }
     }
     cout << f[m] << endl;
     return 0;
}

例题三(背包问题求方案数)

最优解方案数
背包问题求方案数
题意:给我们一个体积为V的背包和n个物品,每个物品都有一个体积v和价值w,然后然后让我们往背包里面装东西,问当价值取到最大时的方案数是多少。
思路:我们考虑状态表示,我们可以设置一个f[i]定义为体积为i时的最佳方案的总价值。cnt[i]表示体积为i时的最佳方案的方案数。那么我们的状态转移就是
第一当取第i个价值更大时我们就先更新 f [ i ] = f [ i − v [ i ] ] f[i]=f[i-v[i]] f[i]=f[iv[i]],然后更新cnt数组 c n t [ i ] = c n t [ i − v [ i ] ] cnt[i]=cnt[i-v[i]] cnt[i]=cnt[iv[i]],如果取第个i和不取第i个的最大价值相等时我们就把cnt更新为 c n t [ i ] = c n t [ i ] + c n t [ i − v [ i ] ] cnt[i]=cnt[i]+cnt[i-v[i]] cnt[i]=cnt[i]+cnt[iv[i]]

#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1e5 + 10, mod = 1e9 + 7;
int f[N], cnt[N];
int v[N], w[N];

int main()
{
     cin.tie(0)->sync_with_stdio(false);
     int n, m;
     cin >> n >> m;
     for (int i = 1; i <= n; i++)
          cin >> v[i] >> w[i];
     fill(cnt, cnt + m + 1, 1);//一开始的时候,最大价值是0
     						   //然后对于每一个v来说都有一个方案就是全都不取
     for (int i = 1; i <= n; i++)
     {
          for (int j = m; j >= v[i]; j--)
          {
               if (f[j] < f[j - v[i]] + w[i])
               {
                    f[j] = f[j - v[i]] + w[i];
                    cnt[j] = cnt[j - v[i]];
               }
               else if (f[j] == f[j - v[i]] + w[i])
               {
                    cnt[j] = (cnt[j] + cnt[j - v[i]]) % mod;
               }
          }
     }
     cout << cnt[m] % mod << endl;
     return 0;
}

例题四(背包问题求具体方案数)

背包问题求具体方案数
题意:和上面的例题三类似,只不过我们要统计具体的方案数,并且要求字典序是最小的。
思路:我们只需要判断哪一个被选了即可,由于我们要求字典序最小所以只要能选我们就选。然后具体的细节在代码里。

#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1e3 + 10;
int v[N], w[N];
int f[N][N];

int main()
{
     cin.tie(0)->sync_with_stdio(false);
     int n, m;
     cin >> n >> m;
     for (int i = 1; i <= n; i++)
          cin >> v[i] >> w[i];
     for (int i = n; i >= 1; i--)//我们从后往前遍历,就相当于我们从最后一个物品开始拿
     {                           //这样便于我们从前往后的统计,即便于输出
          for (int j = 0; j <= m; j++)
          {
               f[i][j] = f[i + 1][j];//由于上述原因所以我们这里是i+1
               if (j >= v[i])
                    f[i][j] = max(f[i][j], f[i + 1][j - v[i]] + w[i]);
          }
     }
     int j = m;
     for (int i = 1; i <= n; i++)
     {
          if (j >= v[i] && f[i][j] == f[i + 1][j - v[i]] + w[i])
          {         //这里也是i+1是因为我们要判断当前这个是否和后面哪一个相等
               cout << i << ' ';
               j -= v[i];
          }
     }
     return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值