背包模型

第二个模型:背包模型

背包问题的种类:
  1. 01背包:每个物品的个数只有一个, 每个物品最多可以选1个
  2. 完全背包: 每个物品的个数有无限个, 不超过背包容积的情况下, 每个物品可选无数个
  3. 多重背包: 每个物品的个数有有限个, 不能超过背包容积, 不能超过该物品给定的个数
  4. 分组背包: 每组物品只能选一个, 每组物品最多只能选一个

01背包:

题目1 链接:01背包问题

在这里插入图片描述

dp数组的初始化:

这里的选择是每个物品选或者不选, 所有的物品都可以都可以通过这个表达式推导出来,因为要求的是最大值, 所以最小值初始化成0即可。

dp数组的下标:

因为状态计算中涉及到 i - 1, 所以我们的数组下标从1开始, 这样写代码时可以省去一些特判

时间复杂度: O ( n ∗ m ) O(n*m) O(nm) 一秒之内可以出解

代码区
#include<iostream>
#include<cstring>

using namespace std;

const int N = 1010;

int n, m;
int f[N][N];

int main()
{
    cin >> n >> m;
    
    for(int i = 1; i <= n; i ++)
    {
        int v, w;
        cin >> v >> w;
        for(int j = 1; j <= m; j ++)
        {
            f[i][j] = f[i - 1][j];
            if(j >= v)
                f[i][j] = max(f[i][j], f[i - 1][j - v] + w);
        }
    }
    
    cout << f[n][m] << endl;
    
    return 0;
}
优化:

根据状态表示: f(i, j) = max( f( i - 1, j ), f( i - 1, j - v ) + w ) 当前这层的值只和 第i - 1 的状态有关,注意这里体积是从大到小进行枚举。 因为j > (j - v), 如果我们从小到大枚举体积, 那么我们在枚举体积j时, j - v 已经被更新过了。此时已经是第i层的值了。如果我们从大到小进行枚举体积:在更新f(i, j )时, 用的f(i - 1, j - v)是 j - 1 层的值, 和优化之前的意思是等价的。

优化后的代码:
#include<iostream>
#include<cstring>

using namespace std;

const int N = 1010;

int n, m;
int f[N];

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

完全背包:

题目1 链接:完全背包问题

分析:

在这里插入图片描述


dp数组的初始化:

每个物品都可以选0, 1,… s个, 所以每个物品都可以通过上述的状态转移方程推导出来。所以不需要初始化

解法一:

时间复杂度: O ( n ∗ m ∗ s ) O(n*m*s) O(nms) 这里不加优化会超时, 因为数据范围 n = 1000, n 3 n^3 n3 = 1e9, 一秒之内出不了结果

代码区:
#include<iostream>
#include<cstring>

using namespace std;

const int N = 1010;

int n, m;
int f[N][N];

int main()
{
    cin >> n >> m;
    
    for(int i = 1; i <= n; i ++)
    {
        int w, v;
        cin >> v >> w;
        for(int j = 1; j <= m; j ++)
        {
            for(int k = 0; k * v <= j; k ++)
            {
                f[i][j] = max(f[i][j], f[i - 1][j - k * v] + k * w);
            }
        }
    }
    
    cout  << f[n][m] << endl;
    
    return 0;
}
优化:

通过状态转移方程 f(i, j) = max( f(i, j), f(i - 1, j - k * v) + k * w) 展开

f(i, j) = max(f(i - 1, j),  f(i - 1, j - v) + w, f(i - 1, j - 2 * v) + 2 * w + ... + f(i - 1, j - s * v) + s * w);
f(i, j - v) = max(          f(i - 1, j - v),     f(i - 1, j - 2 * v) + w,    + ... + f(i - 1, j - s * v) + (s - 1) * w)

宗上所述: f(i, j) = max(f(i - 1, j), f(i, j - v) + w);
为什么完全背包枚举体积时要从小到大进行枚举?

01背包的状态转移方程: f(i, j) = max( f(i - 1, j), f(i - 1, j - v) + w)
完全背包优化后的状态转移方程: f(i, j) = max( f(i - 1, j), f(i , j - v) + w)

01背包是将体积从大到小进行枚举, 而完全背包是将体积从小到大进行枚举的原因主要是因为:

01背包枚举的是f(i - 1, j - v) + w, 完全背包枚举的是f(i, j - v) + w, 因为01背包使用的是第i - 1 层的值, 所以要从大到小进行枚举,确保更新j的时候, j - v是第i - 1层的值。
完全背包枚举的是 f(i, j - v) + w, 确保更新j的时候, j - v 是第i层的值, 所以要从小到大进行枚举。这样更新j之前就更新了j - v的值, 此时j - v已经在之前更新为第i层的值。

优化后的代码:

时间复杂度: O ( n ∗ m ) O(n*m) O(nm), 一秒之内可以出解

#include<iostream>
#include<cstring>

using namespace std;

const int N = 1010;

int n, m;
int f[N];

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

多重背包:

题目1 链接:多重背包问题1

分析:

在这里插入图片描述

dp数组的初始化:

因为状态转移方程: f(i, j) = max( f(i, j), f(i - 1, j - k * v) + k * w), 枚举了每个物品选0, 1, …, s个,每个物品都能通过上述的状态转移方程算出,所以不用针对某些位置的进行初始化

时间复杂度: O ( n ∗ m ∗ s ) O(n * m * s) O(nms) 这题的数据范围比较小, 一秒之内可以出解

代码区:

#include<iostream>
#include<cstring>

using namespace std;

const int N = 110;

int n, m;
int f[N][N];

int main()
{
    cin >> n >> m;
    
    for(int i = 1; i <= n; i ++)
    {
        int v, w, s;
        cin >> v >> w >> s;
        for(int j = 1; j <= m; j ++)
        {
            for(int k = 0; k * v <= j && k <= s; k ++)
            {
                f[i][j] = max(f[i][j], f[i - 1][j - k * v] + k * w);
            }
        }
    }
    
    cout << f[n][m] << endl;
    
    
    return 0;
}

题目2 链接:多重背包问题2

分析:

因为这题的n, m, s数据范围较大,直接暴力做的时间复杂度为 O ( n ∗ m ∗ s ) O(n * m * s) O(nms) ,复杂度是1e9, 1秒之内算不出答案,所以要考虑进行优化。

使用完全背包的优化方式可以优化多重背包吗?(不可以)

首先多重背包和完全背包的区别就是多重背包多了一个要求:即物品的个数是有限的
在完全背包中我们只是因为背包的容积有限。而在这,我们只需要物品的个数受限,看表达式和完全背包的优化后的表达式是否等价。

原式子展开:

f(i, j) = max(f(i - 1, j), f(i - 1, j - v) +  w, f(i - 1, j - 2v) + 2w, f(i - 1, j - sv) + sw)
f(i, j - v) = max(         f(i - 1, j - v),      f(i - 1, j - 2v) + w,  f(i - 1, j - sv) + (s - 1)w, f(i - 1, j - (s + 1)v) + sw)

观察可知,f(i, j - v)f(i, j), 多了 f(i - 1, j - (s + 1)v) + sw, 我们只知道 **f(i, j - v)**的最大值是求不出 f(i, j - v) 的最大值的, 因为如果最大值在 ** f(i - 1, j - (s + 1)v) + sw**这一项取得, 那么我们是无法求出 f(i, j - v) 的最大值的, 所以这个优化方式是不行的。

正确的优化方式:

通过暴力的做法我们可以知道, 我们总共有3重循环,分别枚举物品, 体积, 每个物品选择的个数。这里我们可以对每个物品选择的个数这重循环进行优化。

优化方式:二进制优化, 因为十进制整数肯定也能使用二进制表示出来。

分析:

拆分成二进制之后, 把他们看成不同的物品, 每个物品选或者不选,跑一遍01背包即可

时间复杂度: O ( n ∗ n ∗ l o g n ) O(n * n * logn) O(nnlogn), 1e7一秒之内可以出解

代码区:

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 11010;

int n, m;
int cnt;
int w[N], v[N];
int f[N];

int main()
{
    cin >> n >> m;
    
    for(int i = 0; i < n; i ++)
    {
        int k = 1;
        int a, b, s;
        cin >> a >> b >> s;
        while(k <= s)
        {
            ++ cnt;
            v[cnt] = a * k;
            w[cnt] = b * k;
            s -= k;
            k *= 2;
        }
        if(s > 0)
        {
            ++ cnt;
            v[cnt] = a * s;
            w[cnt] = b * s;
        }
    }
    
    n = cnt;
    for(int i = 1; i <= n; i ++)
    {
        for(int j = m; j >= v[i]; j --)
        {
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        }
    }
    
    cout << f[m] << endl;
    
    return 0;
}
关于多重背包的单调队列优化比较复杂,这里暂时不写

分组背包:

题目2 链接:分组背包问题

分析:

在这里插入图片描述
时间复杂度: O ( n ∗ m ∗ s ) O(n * m * s) O(nms), 量级:1e6, 1秒之内可以出解

代码区:

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 110;

int n, m;
int s[N], v[N][N], w[N][N];
int f[N][N];

int main()
{
    cin >> n >> m;
    
    for(int i = 1; i <= n; i ++)
    {
        cin >> s[i];
        for(int j = 1; j <= s[i]; j ++)
        {
            cin >> v[i][j] >> w[i][j];
        }
    }
    
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 1; j <= m; j ++)
        {
            for(int k = 0; k <= s[i]; k ++)
            {
                if(j >= v[i][k])
                f[i][j] = max(f[i][j], f[i - 1][j - v[i][k]] + w[i][k]);
            }
        }
    }
    
    cout << f[n][m] << endl;
    
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值